source: trunk/exports/G2export_CIF.py @ 3465

Last change on this file since 3465 was 3465, checked in by vondreele, 3 years ago

deal with export/import issues for mag cif files
fix bug for new phase - append atom (space group change problem)

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Author Revision URL Id
File size: 144.1 KB
Line 
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3########### SVN repository information ###################
4# $Date: 2018-07-10 16:41:00 +0000 (Tue, 10 Jul 2018) $
5# $Author: vondreele $
6# $Revision: 3465 $
7# $URL: trunk/exports/G2export_CIF.py $
8# $Id: G2export_CIF.py 3465 2018-07-10 16:41:00Z vondreele $
9########### SVN repository information ###################
10'''
11*Module G2export_CIF: CIF Exports*
12------------------------------------------------------
13
14This implements a complex exporter :class:`ExportCIF` that can implement an
15entire project in a complete CIF intended for submission as a
16publication. In addition, there are three subclasses of :class:`ExportCIF`:
17:class:`ExportProjectCIF`,
18:class:`ExportPhaseCIF` and :class:`ExportDataCIF` where extra parameters
19for the _Exporter() determine if a project, single phase or data set are written.
20'''
21
22from __future__ import division, print_function
23import platform
24import datetime as dt
25import os.path
26import sys
27import numpy as np
28if '2' in platform.python_version_tuple()[0]:
29    import cPickle as pickle
30else:
31    import pickle
32import copy
33import re
34try:
35    import wx
36    import wx.lib.scrolledpanel as wxscroll
37    import wx.lib.resizewidget as rw
38except ImportError:
39    # Avoid wx dependency for CLI
40    class Placeholder(object):
41        def __init__(self):
42            self.BoxSizer = object
43            self.Button = object
44            self.Dialog = object
45            self.ScrolledPanel = object
46    wx = Placeholder()
47    wxscroll = Placeholder()
48import GSASIIpath
49GSASIIpath.SetVersionNumber("$Revision: 3465 $")
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                spacegroup = phasedict['General']['SGData']['MagSpGrp'].strip()
1251                spacegroup = spacegroup[0].upper() + spacegroup[1:].lower().rstrip('rh ')
1252                WriteCIFitem(self.fp, '_space_group_magn.name_BNS',spacegroup)
1253                # generate symmetry operations including centering and center of symmetry
1254                SymOpList,offsetList,symOpList,G2oprList,G2opcodes = G2spc.AllOps(
1255                    phasedict['General']['SGData'])
1256                SpnFlp = phasedict['General']['SGData']['SpnFlp']
1257                WriteCIFitem(self.fp, 'loop_\n    _space_group_symop_magn_operation.id\n    _space_group_symop_magn_operation.xyz')
1258                for i,op in enumerate(SymOpList,start=1):
1259                    if SpnFlp[i-1] >0:
1260                        opr = op.lower()+',+1'
1261                    else:
1262                        opr = op.lower()+',-1'
1263                    WriteCIFitem(self.fp, '   {:3d}  {:}'.format(i,opr))
1264
1265            # loop over histogram(s) used in this phase
1266            if not oneblock and not self.quickmode and not hist:
1267                # report pointers to the histograms used in this phase
1268                histlist = []
1269                for hist in self.Phases[phasenam]['Histograms']:
1270                    if self.Phases[phasenam]['Histograms'][hist]['Use']:
1271                        if phasebyhistDict.get(hist):
1272                            phasebyhistDict[hist].append(phasenam)
1273                        else:
1274                            phasebyhistDict[hist] = [phasenam,]
1275                        blockid = datablockidDict.get(hist)
1276                        if not blockid:
1277                            print("Internal error: no block for data. Phase "+str(
1278                                phasenam)+" histogram "+str(hist))
1279                            histlist = []
1280                            break
1281                        histlist.append(blockid)
1282
1283                if len(histlist) == 0:
1284                    WriteCIFitem(self.fp, '# Note: phase has no associated data')
1285
1286            # report atom params
1287            if phasedict['General']['Type'] in ['nuclear','macromolecular']:        #this needs macromolecular variant, etc!
1288                try:
1289                    self.labellist
1290                except AttributeError:
1291                    self.labellist = []
1292                WriteAtomsNuclear(self.fp, self.Phases[phasenam], phasenam,
1293                                  self.parmDict, self.sigDict, self.labellist)
1294            else:
1295                try:
1296                    self.labellist
1297                except AttributeError:
1298                    self.labellist = []
1299                WriteAtomsMagnetic(self.fp, self.Phases[phasenam], phasenam,
1300                                  self.parmDict, self.sigDict, self.labellist)
1301#                self.CloseFile()
1302#                raise Exception("no export for "+str(phasedict['General']['Type'])+" coordinates implemented")
1303            # report cell contents
1304            WriteComposition(self.fp, self.Phases[phasenam], phasenam, self.parmDict)
1305            if not self.quickmode and phasedict['General']['Type'] == 'nuclear':      # report distances and angles
1306                WriteDistances(phasenam,SymOpList,offsetList,symOpList,G2oprList)
1307            if 'Map' in phasedict['General'] and 'minmax' in phasedict['General']['Map']:
1308                WriteCIFitem(self.fp, '\n# Difference density results')
1309                MinMax = phasedict['General']['Map']['minmax']
1310                WriteCIFitem(self.fp, '_refine_diff_density_max',G2mth.ValEsd(MinMax[0],-0.009))
1311                WriteCIFitem(self.fp, '_refine_diff_density_min',G2mth.ValEsd(MinMax[1],-0.009))
1312
1313        def Yfmt(ndec,val):
1314            'Format intensity values'
1315            out = ("{:."+str(ndec)+"f}").format(val)
1316            out = out.rstrip('0')  # strip zeros to right of decimal
1317            return out.rstrip('.')  # and decimal place when not needed
1318
1319        def WriteReflStat(refcount,hklmin,hklmax,dmin,dmax,nRefSets=1):
1320            'Write reflection statistics'
1321            WriteCIFitem(self.fp, '_reflns_number_total', str(refcount))
1322            if hklmin is not None and nRefSets == 1: # hkl range has no meaning with multiple phases
1323                WriteCIFitem(self.fp, '_reflns_limit_h_min', str(int(hklmin[0])))
1324                WriteCIFitem(self.fp, '_reflns_limit_h_max', str(int(hklmax[0])))
1325                WriteCIFitem(self.fp, '_reflns_limit_k_min', str(int(hklmin[1])))
1326                WriteCIFitem(self.fp, '_reflns_limit_k_max', str(int(hklmax[1])))
1327                WriteCIFitem(self.fp, '_reflns_limit_l_min', str(int(hklmin[2])))
1328                WriteCIFitem(self.fp, '_reflns_limit_l_max', str(int(hklmax[2])))
1329            if hklmin is not None:
1330                WriteCIFitem(self.fp, '_reflns_d_resolution_low  ', G2mth.ValEsd(dmax,-0.009))
1331                WriteCIFitem(self.fp, '_reflns_d_resolution_high ', G2mth.ValEsd(dmin,-0.009))
1332
1333        def WritePowderData(histlbl):
1334            'Write out the selected powder diffraction histogram info'
1335            histblk = self.Histograms[histlbl]
1336            inst = histblk['Instrument Parameters'][0]
1337            hId = histblk['hId']
1338            pfx = ':' + str(hId) + ':'
1339
1340            if 'Lam1' in inst:
1341                ratio = self.parmDict.get('I(L2)/I(L1)',inst['I(L2)/I(L1)'][1])
1342                sratio = self.sigDict.get('I(L2)/I(L1)',-0.0009)
1343                lam1 = self.parmDict.get('Lam1',inst['Lam1'][1])
1344                slam1 = self.sigDict.get('Lam1',-0.00009)
1345                lam2 = self.parmDict.get('Lam2',inst['Lam2'][1])
1346                slam2 = self.sigDict.get('Lam2',-0.00009)
1347                # always assume Ka1 & Ka2 if two wavelengths are present
1348                WriteCIFitem(self.fp, '_diffrn_radiation_type','K\\a~1,2~')
1349                WriteCIFitem(self.fp, 'loop_' +
1350                             '\n   _diffrn_radiation_wavelength' +
1351                             '\n   _diffrn_radiation_wavelength_wt' +
1352                             '\n   _diffrn_radiation_wavelength_id')
1353                WriteCIFitem(self.fp, '  ' + PutInCol(G2mth.ValEsd(lam1,slam1),15)+
1354                             PutInCol('1.0',15) +
1355                             PutInCol('1',5))
1356                WriteCIFitem(self.fp, '  ' + PutInCol(G2mth.ValEsd(lam2,slam2),15)+
1357                             PutInCol(G2mth.ValEsd(ratio,sratio),15)+
1358                             PutInCol('2',5))
1359            elif 'Lam' in inst:
1360                lam1 = self.parmDict.get('Lam',inst['Lam'][1])
1361                slam1 = self.sigDict.get('Lam',-0.00009)
1362                WriteCIFitem(self.fp, '_diffrn_radiation_wavelength',G2mth.ValEsd(lam1,slam1))
1363
1364            if not oneblock:
1365                if not phasebyhistDict.get(histlbl):
1366                    WriteCIFitem(self.fp, '\n# No phases associated with this data set')
1367                else:
1368                    WriteCIFitem(self.fp, '\n# PHASE TABLE')
1369                    WriteCIFitem(self.fp, 'loop_' +
1370                                 '\n   _pd_phase_id' +
1371                                 '\n   _pd_phase_block_id' +
1372                                 '\n   _pd_phase_mass_%')
1373                    wtFrSum = 0.
1374                    for phasenam in phasebyhistDict.get(histlbl):
1375                        hapData = self.Phases[phasenam]['Histograms'][histlbl]
1376                        General = self.Phases[phasenam]['General']
1377                        wtFrSum += hapData['Scale'][0]*General['Mass']
1378
1379                    for phasenam in phasebyhistDict.get(histlbl):
1380                        hapData = self.Phases[phasenam]['Histograms'][histlbl]
1381                        General = self.Phases[phasenam]['General']
1382                        wtFr = hapData['Scale'][0]*General['Mass']/wtFrSum
1383                        pfx = str(self.Phases[phasenam]['pId'])+':'+str(hId)+':'
1384                        if pfx+'Scale' in self.sigDict:
1385                            sig = self.sigDict[pfx+'Scale']*wtFr/hapData['Scale'][0]
1386                        else:
1387                            sig = -0.0001
1388                        WriteCIFitem(self.fp,
1389                            '  '+
1390                            str(self.Phases[phasenam]['pId']) +
1391                            '  '+datablockidDict[phasenam]+
1392                            '  '+G2mth.ValEsd(wtFr,sig)
1393                            )
1394                    WriteCIFitem(self.fp, 'loop_' +
1395                                 '\n   _gsas_proc_phase_R_F_factor' +
1396                                 '\n   _gsas_proc_phase_R_Fsqd_factor' +
1397                                 '\n   _gsas_proc_phase_id' +
1398                                 '\n   _gsas_proc_phase_block_id')
1399                    for phasenam in phasebyhistDict.get(histlbl):
1400                        pfx = str(self.Phases[phasenam]['pId'])+':'+str(hId)+':'
1401                        WriteCIFitem(self.fp,
1402                            '  '+
1403                            '  '+G2mth.ValEsd(histblk[pfx+'Rf']/100.,-.00009) +
1404                            '  '+G2mth.ValEsd(histblk[pfx+'Rf^2']/100.,-.00009)+
1405                            '  '+str(self.Phases[phasenam]['pId'])+
1406                            '  '+datablockidDict[phasenam]
1407                            )
1408            else:
1409                # single phase in this histogram
1410                pfx = '0:'+str(hId)+':'
1411                WriteCIFitem(self.fp, '_refine_ls_R_F_factor      ','%.5f'%(histblk[pfx+'Rf']/100.))
1412                WriteCIFitem(self.fp, '_refine_ls_R_Fsqd_factor   ','%.5f'%(histblk[pfx+'Rf^2']/100.))
1413
1414            WriteCIFitem(self.fp, '_pd_proc_ls_prof_R_factor   ','%.5f'%(histblk['R']/100.))
1415            WriteCIFitem(self.fp, '_pd_proc_ls_prof_wR_factor  ','%.5f'%(histblk['wR']/100.))
1416            WriteCIFitem(self.fp, '_gsas_proc_ls_prof_R_B_factor ','%.5f'%(histblk['Rb']/100.))
1417            WriteCIFitem(self.fp, '_gsas_proc_ls_prof_wR_B_factor','%.5f'%(histblk['wRb']/100.))
1418            WriteCIFitem(self.fp, '_pd_proc_ls_prof_wR_expected','%.5f'%(histblk['wRmin']/100.))
1419
1420            if histblk['Instrument Parameters'][0]['Type'][1][1] == 'X':
1421                WriteCIFitem(self.fp, '_diffrn_radiation_probe','x-ray')
1422                pola = histblk['Instrument Parameters'][0].get('Polariz.')
1423                if pola:
1424                    pfx = ':' + str(hId) + ':'
1425                    sig = self.sigDict.get(pfx+'Polariz.',-0.0009)
1426                    txt = G2mth.ValEsd(pola[1],sig)
1427                    WriteCIFitem(self.fp, '_diffrn_radiation_polarisn_ratio',txt)
1428            elif histblk['Instrument Parameters'][0]['Type'][1][1] == 'N':
1429                WriteCIFitem(self.fp, '_diffrn_radiation_probe','neutron')
1430            if 'T' in inst['Type'][0]:
1431                txt = G2mth.ValEsd(inst['2-theta'][0],-0.009)
1432                WriteCIFitem(self.fp, '_pd_meas_2theta_fixed',txt)
1433
1434            # TODO: this will need help from Bob
1435            #if not oneblock:
1436            #WriteCIFitem(self.fp, '\n# SCATTERING FACTOR INFO')
1437            #WriteCIFitem(self.fp, 'loop_  _atom_type_symbol')
1438            #if histblk['Instrument Parameters'][0]['Type'][1][1] == 'X':
1439            #    WriteCIFitem(self.fp, '      _atom_type_scat_dispersion_real')
1440            #    WriteCIFitem(self.fp, '      _atom_type_scat_dispersion_imag')
1441            #    for lbl in ('a1','a2','a3', 'a4', 'b1', 'b2', 'b3', 'b4', 'c'):
1442            #        WriteCIFitem(self.fp, '      _atom_type_scat_Cromer_Mann_'+lbl)
1443            #elif histblk['Instrument Parameters'][0]['Type'][1][1] == 'N':
1444            #    WriteCIFitem(self.fp, '      _atom_type_scat_length_neutron')
1445            #WriteCIFitem(self.fp, '      _atom_type_scat_source')
1446
1447            WriteCIFitem(self.fp, '_pd_proc_ls_background_function',FormatBackground(histblk['Background'],histblk['hId']))
1448
1449            # TODO: this will need help from Bob
1450            #WriteCIFitem(self.fp, '_exptl_absorpt_process_details','?')
1451            #WriteCIFitem(self.fp, '_exptl_absorpt_correction_T_min','?')
1452            #WriteCIFitem(self.fp, '_exptl_absorpt_correction_T_max','?')
1453            #C extinction
1454            #WRITE(IUCIF,'(A)') '# Extinction correction'
1455            #CALL WRVAL(IUCIF,'_gsas_exptl_extinct_corr_T_min',TEXT(1:10))
1456            #CALL WRVAL(IUCIF,'_gsas_exptl_extinct_corr_T_max',TEXT(11:20))
1457
1458            if not oneblock:                 # instrumental profile terms go here
1459                WriteCIFitem(self.fp, '_pd_proc_ls_profile_function',
1460                    FormatInstProfile(histblk["Instrument Parameters"],histblk['hId']))
1461
1462            #refprx = '_refln.' # mm
1463            refprx = '_refln_' # normal
1464            # data collection parameters for the powder dataset
1465
1466            temperature = histblk['Sample Parameters'].get('Temperature') # G2 uses K
1467            if not temperature:
1468                T = '?'
1469            else:
1470                T = G2mth.ValEsd(temperature,-0.009,True) # CIF uses K
1471            WriteCIFitem(self.fp, '_diffrn_ambient_temperature',T)
1472
1473            pressure = histblk['Sample Parameters'].get('Pressure') #G2 uses mega-Pascal
1474            if not pressure:
1475                P = '?'
1476            else:
1477                P = G2mth.ValEsd(pressure*1000,-0.09,True) # CIF uses kilopascal
1478            WriteCIFitem(self.fp, '_diffrn_ambient_pressure',P)
1479
1480            WriteCIFitem(self.fp, '\n# STRUCTURE FACTOR TABLE')
1481            # compute maximum intensity reflection
1482            Imax = 0
1483            for phasenam in histblk['Reflection Lists']:
1484                scale = self.Phases[phasenam]['Histograms'][histlbl]['Scale'][0]
1485                refList = np.asarray(histblk['Reflection Lists'][phasenam]['RefList'])
1486                I100 = scale*refList.T[8]*refList.T[11]
1487                #Icorr = np.array([refl[13] for refl in histblk['Reflection Lists'][phasenam]])[0]
1488                #FO2 = np.array([refl[8] for refl in histblk['Reflection Lists'][phasenam]])
1489                #I100 = scale*FO2*Icorr
1490                Imax = max(Imax,max(I100))
1491
1492            WriteCIFitem(self.fp, 'loop_')
1493            if len(histblk['Reflection Lists'].keys()) > 1:
1494                WriteCIFitem(self.fp, '   _pd_refln_phase_id')
1495            WriteCIFitem(self.fp, '   ' + refprx + 'index_h' +
1496                         '\n   ' + refprx + 'index_k' +
1497                         '\n   ' + refprx + 'index_l' +
1498                         '\n   ' + refprx + 'F_squared_meas' +
1499                         '\n   ' + refprx + 'F_squared_calc' +
1500                         '\n   ' + refprx + 'phase_calc' +
1501                         '\n   _pd_refln_d_spacing')
1502            if Imax > 0:
1503                WriteCIFitem(self.fp, '   _gsas_i100_meas')
1504
1505            refcount = 0
1506            hklmin = None
1507            hklmax = None
1508            dmax = None
1509            dmin = None
1510            for phasenam in histblk['Reflection Lists']:
1511                scale = self.Phases[phasenam]['Histograms'][histlbl]['Scale'][0]
1512                phaseid = self.Phases[phasenam]['pId']
1513                refcount += len(histblk['Reflection Lists'][phasenam]['RefList'])
1514                refList = np.asarray(histblk['Reflection Lists'][phasenam]['RefList'])
1515                I100 = scale*refList.T[8]*refList.T[11]
1516                for j,ref in enumerate(histblk['Reflection Lists'][phasenam]['RefList']):
1517                    if DEBUG:
1518                        print('DEBUG: skipping reflection list')
1519                        break
1520                    if hklmin is None:
1521                        hklmin = ref[0:3]
1522                        hklmax = ref[0:3]
1523                        dmax = dmin = ref[4]
1524                    if len(histblk['Reflection Lists'].keys()) > 1:
1525                        s = PutInCol(phaseid,2)
1526                    else:
1527                        s = ""
1528                    for i,hkl in enumerate(ref[0:3]):
1529                        hklmax[i] = max(hkl,hklmax[i])
1530                        hklmin[i] = min(hkl,hklmin[i])
1531                        s += PutInCol(int(hkl),4)
1532                    for I in ref[8:10]:
1533                        s += PutInCol(G2mth.ValEsd(I,-0.0009),10)
1534                    s += PutInCol(G2mth.ValEsd(ref[10],-0.9),7)
1535                    dmax = max(dmax,ref[4])
1536                    dmin = min(dmin,ref[4])
1537                    s += PutInCol(G2mth.ValEsd(ref[4],-0.009),8)
1538                    if Imax > 0:
1539                        s += PutInCol(G2mth.ValEsd(100.*I100[j]/Imax,-0.09),6)
1540                    WriteCIFitem(self.fp, "  "+s)
1541
1542            WriteReflStat(refcount,hklmin,hklmax,dmin,dmax,len(histblk['Reflection Lists']))
1543            WriteCIFitem(self.fp, '\n# POWDER DATA TABLE')
1544            # is data fixed step? If the step varies by <0.01% treat as fixed step
1545            steps = histblk['Data'][0][1:] - histblk['Data'][0][:-1]
1546            if abs(max(steps)-min(steps)) > abs(max(steps))/10000.:
1547                fixedstep = False
1548            else:
1549                fixedstep = True
1550
1551            zero = None
1552            if fixedstep and 'T' not in inst['Type'][0]: # and not TOF
1553                WriteCIFitem(self.fp, '_pd_meas_2theta_range_min', G2mth.ValEsd(histblk['Data'][0][0],-0.00009))
1554                WriteCIFitem(self.fp, '_pd_meas_2theta_range_max', G2mth.ValEsd(histblk['Data'][0][-1],-0.00009))
1555                WriteCIFitem(self.fp, '_pd_meas_2theta_range_inc', G2mth.ValEsd(steps.sum()/len(steps),-0.00009))
1556                # zero correct, if defined
1557                zerolst = histblk['Instrument Parameters'][0].get('Zero')
1558                if zerolst: zero = zerolst[1]
1559                zero = self.parmDict.get('Zero',zero)
1560                if zero:
1561                    WriteCIFitem(self.fp, '_pd_proc_2theta_range_min', G2mth.ValEsd(histblk['Data'][0][0]-zero,-0.00009))
1562                    WriteCIFitem(self.fp, '_pd_proc_2theta_range_max', G2mth.ValEsd(histblk['Data'][0][-1]-zero,-0.00009))
1563                    WriteCIFitem(self.fp, '_pd_proc_2theta_range_inc', G2mth.ValEsd(steps.sum()/len(steps),-0.00009))
1564
1565            if zero:
1566                WriteCIFitem(self.fp, '_pd_proc_number_of_points', str(len(histblk['Data'][0])))
1567            else:
1568                WriteCIFitem(self.fp, '_pd_meas_number_of_points', str(len(histblk['Data'][0])))
1569            WriteCIFitem(self.fp, '\nloop_')
1570            #            WriteCIFitem(self.fp, '   _pd_proc_d_spacing') # need easy way to get this
1571            if not fixedstep:
1572                if zero:
1573                    WriteCIFitem(self.fp, '   _pd_proc_2theta_corrected')
1574                elif 'T' in inst['Type'][0]: # and not TOF
1575                    WriteCIFitem(self.fp, '   _pd_meas_time_of_flight')
1576                else:
1577                    WriteCIFitem(self.fp, '   _pd_meas_2theta_scan')
1578            # at least for now, always report weights.
1579            #if countsdata:
1580            #    WriteCIFitem(self.fp, '   _pd_meas_counts_total')
1581            #else:
1582            WriteCIFitem(self.fp, '   _pd_meas_intensity_total')
1583            WriteCIFitem(self.fp, '   _pd_calc_intensity_total')
1584            WriteCIFitem(self.fp, '   _pd_proc_intensity_bkg_calc')
1585            WriteCIFitem(self.fp, '   _pd_proc_ls_weight')
1586            maxY = max(histblk['Data'][1].max(),histblk['Data'][3].max())
1587            if maxY < 0: maxY *= -10 # this should never happen, but...
1588            ndec = max(0,10-int(np.log10(maxY))-1) # 10 sig figs should be enough
1589            maxSU = histblk['Data'][2].max()
1590            if maxSU < 0: maxSU *= -1 # this should never happen, but...
1591            ndecSU = max(0,8-int(np.log10(maxSU))-1) # 8 sig figs should be enough
1592            lowlim,highlim = histblk['Limits'][1]
1593
1594            if DEBUG:
1595                print('DEBUG: skipping profile list')
1596            else:
1597                for x,yobs,yw,ycalc,ybkg in zip(histblk['Data'][0],
1598                                                histblk['Data'][1],
1599                                                histblk['Data'][2],
1600                                                histblk['Data'][3],
1601                                                histblk['Data'][4]):
1602                    if lowlim <= x <= highlim:
1603                        pass
1604                    else:
1605                        yw = 0.0 # show the point is not in use
1606
1607                    if fixedstep:
1608                        s = ""
1609                    elif zero:
1610                        s = PutInCol(G2mth.ValEsd(x-zero,-0.00009),10)
1611                    else:
1612                        s = PutInCol(G2mth.ValEsd(x,-0.00009),10)
1613                    s += PutInCol(Yfmt(ndec,yobs),12)
1614                    s += PutInCol(Yfmt(ndec,ycalc),12)
1615                    s += PutInCol(Yfmt(ndec,ybkg),11)
1616                    s += PutInCol(Yfmt(ndecSU,yw),9)
1617                    WriteCIFitem(self.fp, "  "+s)
1618
1619        def WriteSingleXtalData(histlbl):
1620            'Write out the selected single crystal histogram info'
1621            histblk = self.Histograms[histlbl]
1622
1623            #refprx = '_refln.' # mm
1624            refprx = '_refln_' # normal
1625
1626            WriteCIFitem(self.fp, '\n# STRUCTURE FACTOR TABLE')
1627            WriteCIFitem(self.fp, 'loop_' +
1628                         '\n   ' + refprx + 'index_h' +
1629                         '\n   ' + refprx + 'index_k' +
1630                         '\n   ' + refprx + 'index_l' +
1631                         '\n   ' + refprx + 'F_squared_meas' +
1632                         '\n   ' + refprx + 'F_squared_sigma' +
1633                         '\n   ' + refprx + 'F_squared_calc' +
1634                         '\n   ' + refprx + 'phase_calc'
1635                         )
1636
1637            hklmin = None
1638            hklmax = None
1639            dmax = None
1640            dmin = None
1641            refcount = len(histblk['Data']['RefList'])
1642            for ref in histblk['Data']['RefList']:
1643                if ref[3] <= 0:      #skip user rejected reflections (mul <= 0)
1644                    continue
1645                s = "  "
1646                if hklmin is None:
1647                    hklmin = ref[0:3]
1648                    hklmax = ref[0:3]
1649                    dmax = dmin = ref[4]
1650                for i,hkl in enumerate(ref[0:3]):
1651                    hklmax[i] = max(hkl,hklmax[i])
1652                    hklmin[i] = min(hkl,hklmin[i])
1653                    s += PutInCol(int(hkl),4)
1654                if ref[5] == 0.0:
1655                    s += PutInCol(G2mth.ValEsd(ref[8],0),12)
1656                    s += PutInCol('.',10)
1657                    s += PutInCol(G2mth.ValEsd(ref[9],0),12)
1658                    s += PutInCol(G2mth.ValEsd(ref[10],-0.9),7)
1659                else:
1660                    sig = ref[6] * ref[8] / ref[5]
1661                    s += PutInCol(G2mth.ValEsd(ref[8],-abs(sig/10)),12)
1662                    s += PutInCol(G2mth.ValEsd(sig,-abs(sig)/10.),10)
1663                    s += PutInCol(G2mth.ValEsd(ref[9],-abs(sig/10)),12)
1664                s += PutInCol(G2mth.ValEsd(ref[10],-0.9),7)
1665                dmax = max(dmax,ref[4])
1666                dmin = min(dmin,ref[4])
1667                WriteCIFitem(self.fp, s)
1668            if not self.quickmode: # statistics only in a full CIF
1669                WriteReflStat(refcount,hklmin,hklmax,dmin,dmax)
1670                hId = histblk['hId']
1671                hfx = '0:'+str(hId)+':'
1672                phfx = '%d:%d:'%(0,hId)
1673                extType,extModel,extParms = self.Phases[phasenam]['Histograms'][histlbl]['Extinction']
1674                if extModel != 'None':
1675                    WriteCIFitem(self.fp, '# Extinction scaled by 1.e5')
1676                    WriteCIFitem(self.fp, '_refine_ls_extinction_method','Becker-Coppens %s %s'%(extModel,extType))
1677                    sig = -1.e-3
1678                    if extModel == 'Primary':
1679                        parm = extParms['Ep'][0]*1.e5
1680                        if extParms['Ep'][1]:
1681                            sig = self.sigDict[phfx+'Ep']*1.e5
1682                        text = G2mth.ValEsd(parm,sig)
1683                    elif extModel == 'Secondary Type I':
1684                        parm = extParms['Eg'][0]*1.e5
1685                        if extParms['Eg'][1]:
1686                            sig = self.sigDict[phfx+'Eg']*1.e5
1687                        text = G2mth.ValEsd(parm,sig)
1688                    elif extModel == 'Secondary Type II':
1689                        parm = extParms['Es'][0]*1.e5
1690                        if extParms['Es'][1]:
1691                            sig = self.sigDict[phfx+'Es']*1.e5
1692                        text = G2mth.ValEsd(parm,sig)
1693                    elif extModel == 'Secondary Type I & II':
1694                        parm = extParms['Eg'][0]*1.e5
1695                        if extParms['Es'][1]:
1696                            sig = self.sigDict[phfx+'Es']*1.e5
1697                        text = G2mth.ValEsd(parm,sig)
1698                        sig = -1.0e-3
1699                        parm = extParms['Es'][0]*1.e5
1700                        if extParms['Es'][1]:
1701                            sig = self.sigDict[phfx+'Es']*1.e5
1702                        text += G2mth.ValEsd(parm,sig)
1703                    WriteCIFitem(self.fp, '_refine_ls_extinction_coef',text)
1704                    WriteCIFitem(self.fp, '_refine_ls_extinction_expression','Becker & Coppens (1974). Acta Cryst. A30, 129-147')
1705
1706                WriteCIFitem(self.fp, '_refine_ls_wR_factor_gt    ','%.4f'%(histblk['wR']/100.))
1707                WriteCIFitem(self.fp, '_refine_ls_R_factor_gt     ','%.4f'%(histblk[hfx+'Rf']/100.))
1708                WriteCIFitem(self.fp, '_refine_ls_R_Fsqd_factor   ','%.4f'%(histblk[hfx+'Rf^2']/100.))
1709        def EditAuthor(event=None):
1710            'dialog to edit the CIF author info'
1711            'Edit the CIF author name'
1712            dlg = G2G.SingleStringDialog(self.G2frame,
1713                                          'Get CIF Author',
1714                                          'Provide CIF Author name (Last, First)',
1715                                          value=self.author)
1716            if not dlg.Show():
1717                dlg.Destroy()
1718                return False  # cancel was pressed
1719            self.author = dlg.GetValue()
1720            self.shortauthorname = self.author.replace(',','').replace(' ','')[:20]
1721            dlg.Destroy()
1722            try:
1723                self.OverallParms['Controls']["Author"] = self.author # save for future
1724            except KeyError:
1725                pass
1726            return True
1727
1728        def EditInstNames(event=None):
1729            'Provide a dialog for editing instrument names'
1730            dictlist = []
1731            keylist = []
1732            lbllist = []
1733            for hist in self.Histograms:
1734                if hist.startswith("PWDR"):
1735                    key2 = "Sample Parameters"
1736                    d = self.Histograms[hist][key2]
1737                elif hist.startswith("HKLF"):
1738                    key2 = "Instrument Parameters"
1739                    d = self.Histograms[hist][key2][0]
1740
1741                lbllist.append(hist)
1742                dictlist.append(d)
1743                keylist.append('InstrName')
1744                instrname = d.get('InstrName')
1745                if instrname is None:
1746                    d['InstrName'] = ''
1747            return G2G.CallScrolledMultiEditor(
1748                self.G2frame,dictlist,keylist,
1749                prelbl=range(1,len(dictlist)+1),
1750                postlbl=lbllist,
1751                title='Instrument names',
1752                header="Edit instrument names. Note that a non-blank\nname is required for all histograms",
1753                CopyButton=True,ASCIIonly=True)
1754
1755        def EditRanges(event):
1756            '''Edit the bond distance/angle search range; phase is determined from
1757            a pointer placed in the button object (.phasedict) that references the
1758            phase dictionary
1759            '''
1760            but = event.GetEventObject()
1761            phasedict = but.phasedict
1762            dlg = G2G.DisAglDialog(
1763                self.G2frame,
1764                phasedict['General']['DisAglCtls'], # edited
1765                phasedict['General'], # defaults
1766                )
1767            if dlg.ShowModal() == wx.ID_OK:
1768                phasedict['General']['DisAglCtls'] = dlg.GetData()
1769            dlg.Destroy()
1770
1771        def EditCIFDefaults():
1772            '''Fills the CIF Defaults window with controls for editing various CIF export
1773            parameters (mostly related to templates).
1774            '''
1775            self.cifdefs.DestroyChildren()
1776            self.cifdefs.SetTitle('Edit CIF settings')
1777            vbox = wx.BoxSizer(wx.VERTICAL)
1778            vbox.Add(wx.StaticText(self.cifdefs, wx.ID_ANY,'Creating file '+self.filename))
1779            but = wx.Button(self.cifdefs, wx.ID_ANY,'Edit CIF Author')
1780            but.Bind(wx.EVT_BUTTON,EditAuthor)
1781            vbox.Add(but,0,wx.ALIGN_CENTER,3)
1782            but = wx.Button(self.cifdefs, wx.ID_ANY,'Edit Instrument Name(s)')
1783            but.Bind(wx.EVT_BUTTON,EditInstNames)
1784            vbox.Add(but,0,wx.ALIGN_CENTER,3)
1785            cpnl = wxscroll.ScrolledPanel(self.cifdefs,size=(300,300))
1786            cbox = wx.BoxSizer(wx.VERTICAL)
1787            G2G.HorizontalLine(cbox,cpnl)
1788            cbox.Add(
1789                CIFtemplateSelect(self.cifdefs,
1790                                  cpnl,'publ',self.OverallParms['Controls'],
1791                                  EditCIFDefaults,
1792                                  "Publication (overall) template",
1793                                  ),
1794                0,wx.EXPAND|wx.ALIGN_LEFT|wx.ALL)
1795            for phasenam in sorted(self.Phases.keys()):
1796                G2G.HorizontalLine(cbox,cpnl)
1797                title = 'Phase '+phasenam
1798                phasedict = self.Phases[phasenam] # pointer to current phase info
1799                cbox.Add(
1800                    CIFtemplateSelect(self.cifdefs,
1801                                      cpnl,'phase',phasedict['General'],
1802                                      EditCIFDefaults,
1803                                      title,
1804                                      phasenam),
1805                    0,wx.EXPAND|wx.ALIGN_LEFT|wx.ALL)
1806                cpnl.SetSizer(cbox)
1807                if phasedict['General']['Type'] == 'nuclear':
1808                    but = wx.Button(cpnl, wx.ID_ANY,'Edit distance/angle ranges')
1809                    cbox.Add(but,0,wx.ALIGN_LEFT,0)
1810                    cbox.Add((-1,2))
1811                    but.phasedict = self.Phases[phasenam]  # set a pointer to current phase info
1812                    but.Bind(wx.EVT_BUTTON,EditRanges)     # phase bond/angle ranges
1813                    but = wx.Button(cpnl, wx.ID_ANY,'Set distance/angle publication flags')
1814                    but.phase = phasenam  # set a pointer to current phase info
1815                    but.Bind(wx.EVT_BUTTON,SelectDisAglFlags)     # phase bond/angle ranges
1816                    cbox.Add(but,0,wx.ALIGN_LEFT,0)
1817                cbox.Add((-1,2))
1818            for i in sorted(self.powderDict.keys()):
1819                G2G.HorizontalLine(cbox,cpnl)
1820                hist = self.powderDict[i]
1821                histblk = self.Histograms[hist]
1822                title = 'Powder dataset '+hist[5:]
1823                cbox.Add(
1824                    CIFtemplateSelect(self.cifdefs,
1825                                      cpnl,'powder',histblk["Sample Parameters"],
1826                                      EditCIFDefaults,
1827                                      title,
1828                                      histblk["Sample Parameters"]['InstrName']),
1829                    0,wx.EXPAND|wx.ALIGN_LEFT|wx.ALL)
1830            for i in sorted(self.xtalDict.keys()):
1831                G2G.HorizontalLine(cbox,cpnl)
1832                hist = self.xtalDict[i]
1833                histblk = self.Histograms[hist]
1834                title = 'Single Xtal dataset '+hist[5:]
1835                cbox.Add(
1836                    CIFtemplateSelect(self.cifdefs,
1837                                      cpnl,'single',histblk["Instrument Parameters"][0],
1838                                      EditCIFDefaults,
1839                                      title,
1840                                      histblk["Instrument Parameters"][0]['InstrName']),
1841                    0,wx.EXPAND|wx.ALIGN_LEFT|wx.ALL)
1842            cpnl.SetSizer(cbox)
1843            cpnl.SetAutoLayout(1)
1844            cpnl.SetupScrolling()
1845            #cpnl.Bind(rw.EVT_RW_LAYOUT_NEEDED, self.OnLayoutNeeded) # needed if sizes change
1846            cpnl.Layout()
1847
1848            vbox.Add(cpnl, 1, wx.ALIGN_LEFT|wx.ALL|wx.EXPAND, 0)
1849            btnsizer = wx.StdDialogButtonSizer()
1850            btn = wx.Button(self.cifdefs, wx.ID_OK, "Create CIF")
1851            btn.SetDefault()
1852            btnsizer.AddButton(btn)
1853            btn = wx.Button(self.cifdefs, wx.ID_CANCEL)
1854            btnsizer.AddButton(btn)
1855            btnsizer.Realize()
1856            vbox.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5)
1857            self.cifdefs.SetSizer(vbox)
1858            vbox.Fit(self.cifdefs)
1859            self.cifdefs.Layout()
1860
1861        def OnToggleButton(event):
1862            'Respond to press of ToggleButton in SelectDisAglFlags'
1863            but = event.GetEventObject()
1864            if but.GetValue():
1865                but.DisAglSel[but.key] = True
1866            else:
1867                try:
1868                    del but.DisAglSel[but.key]
1869                except KeyError:
1870                    pass
1871        def keepTrue(event):
1872            event.GetEventObject().SetValue(True)
1873        def keepFalse(event):
1874            event.GetEventObject().SetValue(False)
1875
1876        def SelectDisAglFlags(event):
1877            'Select Distance/Angle use flags for the selected phase'
1878            phasenam = event.GetEventObject().phase
1879            phasedict = self.Phases[phasenam]
1880            SymOpList,offsetList,symOpList,G2oprList,G2opcodes = G2spc.AllOps(phasedict['General']['SGData'])
1881            generalData = phasedict['General']
1882            # create a dict for storing Pub flag for bonds/angles, if needed
1883            if phasedict['General'].get("DisAglHideFlag") is None:
1884                phasedict['General']["DisAglHideFlag"] = {}
1885            DisAngSel = phasedict['General']["DisAglHideFlag"]
1886
1887            cx,ct,cs,cia = phasedict['General']['AtomPtrs']
1888            cn = ct-1
1889            cfrac = cx+3
1890            DisAglData = {}
1891            # create a list of atoms, but skip atoms with zero occupancy
1892            xyz = []
1893            fpfx = str(phasedict['pId'])+'::Afrac:'
1894            for i,atom in enumerate(phasedict['Atoms']):
1895                if self.parmDict.get(fpfx+str(i),atom[cfrac]) == 0.0: continue
1896                xyz.append([i,]+atom[cn:cn+2]+atom[cx:cx+3])
1897            if 'DisAglCtls' not in generalData:
1898                # should not be used, since DisAglDialog should be called
1899                # for all phases before getting here
1900                dlg = G2G.DisAglDialog(
1901                    self.cifdefs,
1902                    {},
1903                    generalData)
1904                if dlg.ShowModal() == wx.ID_OK:
1905                    generalData['DisAglCtls'] = dlg.GetData()
1906                else:
1907                    dlg.Destroy()
1908                    return
1909                dlg.Destroy()
1910            dlg = wx.Dialog(
1911                self.G2frame,
1912                style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
1913            vbox = wx.BoxSizer(wx.VERTICAL)
1914            txt = wx.StaticText(dlg,wx.ID_ANY,'Searching distances for phase '+phasenam
1915                                +'\nPlease wait...')
1916            vbox.Add(txt,0,wx.ALL|wx.EXPAND)
1917            dlg.SetSizer(vbox)
1918            dlg.CenterOnParent()
1919            dlg.Show() # post "please wait"
1920            wx.BeginBusyCursor() # and change cursor
1921
1922            DisAglData['OrigAtoms'] = xyz
1923            DisAglData['TargAtoms'] = xyz
1924            SymOpList,offsetList,symOpList,G2oprList,G2opcodes = G2spc.AllOps(
1925                generalData['SGData'])
1926
1927#            xpandSGdata = generalData['SGData'].copy()
1928#            xpandSGdata.update({'SGOps':symOpList,
1929#                                'SGInv':False,
1930#                                'SGLatt':'P',
1931#                                'SGCen':np.array([[0, 0, 0]]),})
1932#            DisAglData['SGData'] = xpandSGdata
1933            DisAglData['SGData'] = generalData['SGData'].copy()
1934
1935            DisAglData['Cell'] = generalData['Cell'][1:] #+ volume
1936            if 'pId' in phasedict:
1937                DisAglData['pId'] = phasedict['pId']
1938                DisAglData['covData'] = self.OverallParms['Covariance']
1939            try:
1940                AtomLabels,DistArray,AngArray = G2stMn.RetDistAngle(
1941                    generalData['DisAglCtls'],
1942                    DisAglData)
1943            except KeyError:        # inside DistAngle for missing atom types in DisAglCtls
1944                print('**** ERROR - try again but do "Reset" to fill in missing atom types ****')
1945            wx.EndBusyCursor()
1946            txt.SetLabel('Set publication flags for distances and angles in\nphase '+phasenam)
1947            vbox.Add((5,5))
1948            vbox.Add(wx.StaticText(dlg,wx.ID_ANY,
1949                                   'The default is to flag all distances and angles as to be'+
1950                                   '\npublished. Change this by pressing appropriate buttons.'),
1951                     0,wx.ALL|wx.EXPAND)
1952            hbox = wx.BoxSizer(wx.HORIZONTAL)
1953            vbox.Add(hbox)
1954            hbox.Add(wx.StaticText(dlg,wx.ID_ANY,'Button appearance: '))
1955            but = wx.ToggleButton(dlg,wx.ID_ANY,'Publish')
1956            but.Bind(wx.EVT_TOGGLEBUTTON,keepFalse)
1957            hbox.Add(but)
1958            but = wx.ToggleButton(dlg,wx.ID_ANY,"Don't publish")
1959            but.Bind(wx.EVT_TOGGLEBUTTON,keepTrue)
1960            hbox.Add(but)
1961            but.SetValue(True)
1962            G2G.HorizontalLine(vbox,dlg)
1963
1964            cpnl = wxscroll.ScrolledPanel(dlg,size=(400,300))
1965            cbox = wx.BoxSizer(wx.VERTICAL)
1966            for c in sorted(DistArray):
1967                karr = []
1968                UsedCols = {}
1969                cbox.Add(wx.StaticText(cpnl,wx.ID_ANY,
1970                                   'distances to/angles around atom '+AtomLabels[c]))
1971                #dbox = wx.GridBagSizer(hgap=5)
1972                dbox = wx.GridBagSizer()
1973                for i,D in enumerate(DistArray[c]):
1974                    karr.append(tuple(D[0:3]))
1975                    val = "{:.2f}".format(D[3])
1976                    sym = " [{:d} {:d} {:d}]".format(*D[1]) + " #{:d}".format(D[2])
1977                    dbox.Add(wx.StaticText(cpnl,wx.ID_ANY,AtomLabels[D[0]]),
1978                             (i+1,0)
1979                             )
1980                    dbox.Add(wx.StaticText(cpnl,wx.ID_ANY,sym),
1981                             (i+1,1)
1982                             )
1983                    but = wx.ToggleButton(cpnl,wx.ID_ANY,val)
1984                    but.key = (c,karr[-1])
1985                    but.DisAglSel = DisAngSel
1986                    if DisAngSel.get(but.key): but.SetValue(True)
1987                    but.Bind(wx.EVT_TOGGLEBUTTON,OnToggleButton)
1988                    dbox.Add(but,(i+1,2),border=1)
1989                for i,D in enumerate(AngArray[c]):
1990                    val = "{:.1f}".format(D[2][0])
1991                    but = wx.ToggleButton(cpnl,wx.ID_ANY,val)
1992                    but.key = (karr[D[0]],c,karr[D[1]])
1993                    but.DisAglSel = DisAngSel
1994                    if DisAngSel.get(but.key): but.SetValue(True)
1995                    but.Bind(wx.EVT_TOGGLEBUTTON,OnToggleButton)
1996                    dbox.Add(but,(D[0]+1,D[1]+3),border=1)
1997                    UsedCols[D[1]+3] = True
1998                for i,D in enumerate(DistArray[c][:-1]): # label columns that are used
1999                    if UsedCols.get(i+3):
2000                        dbox.Add(wx.StaticText(cpnl,wx.ID_ANY,AtomLabels[D[0]]),
2001                                 (0,i+3),
2002                                 flag=wx.ALIGN_CENTER
2003                                 )
2004                dbox.Add(wx.StaticText(cpnl,wx.ID_ANY,'distance'),
2005                                 (0,2),
2006                                 flag=wx.ALIGN_CENTER
2007                                 )
2008                cbox.Add(dbox)
2009                G2G.HorizontalLine(cbox,cpnl)
2010            cpnl.SetSizer(cbox)
2011            cpnl.SetAutoLayout(1)
2012            cpnl.SetupScrolling()
2013            #cpnl.Bind(rw.EVT_RW_LAYOUT_NEEDED, self.OnLayoutNeeded) # needed if sizes change
2014            cpnl.Layout()
2015
2016            vbox.Add(cpnl, 1, wx.ALIGN_LEFT|wx.ALL|wx.EXPAND, 0)
2017
2018            btnsizer = wx.StdDialogButtonSizer()
2019            btn = wx.Button(dlg, wx.ID_OK, "Done")
2020            btn.SetDefault()
2021            btnsizer.AddButton(btn)
2022            btnsizer.Realize()
2023            vbox.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5)
2024            dlg.SetSizer(vbox)
2025            vbox.Fit(dlg)
2026            dlg.Layout()
2027
2028            dlg.CenterOnParent()
2029            dlg.ShowModal()
2030
2031#=================================================================================
2032#===== end of function definitions for _Exporter =================================
2033#=================================================================================
2034        # make sure required information is present
2035        self.CIFdate = dt.datetime.strftime(dt.datetime.now(),"%Y-%m-%dT%H:%M")
2036        if not self.CIFname: # Get a name for the CIF. If not defined, use the GPX name (save, if that is needed).
2037            if not self.G2frame.GSASprojectfile:
2038                self.G2frame.OnFileSaveas(None)
2039            if not self.G2frame.GSASprojectfile: return
2040            self.CIFname = os.path.splitext(
2041                os.path.split(self.G2frame.GSASprojectfile)[1]
2042                )[0]
2043            self.CIFname = self.CIFname.replace(' ','')
2044        # replace non-ASCII characters in CIFname with dots
2045        s = ''
2046        for c in self.CIFname:
2047            if ord(c) < 128:
2048                s += c
2049            else:
2050                s += '.'
2051        self.CIFname = s
2052        # load saved CIF author name
2053        try:
2054            self.author = self.OverallParms['Controls'].get("Author",'').strip()
2055        except KeyError:
2056            pass
2057        #=================================================================
2058        # write quick CIFs
2059        #=================================================================
2060        if phaseOnly: #====Phase only CIF ================================
2061            print('Writing CIF output to file '+self.filename)
2062            #self.OpenFile()
2063            oneblock = True
2064            self.quickmode = True
2065            self.Write(' ')
2066            self.Write(70*'#')
2067            WriteCIFitem(self.fp, 'data_'+self.CIFname)
2068            #phaseblk = self.Phases[phaseOnly] # pointer to current phase info
2069            # report the phase info
2070            WritePhaseInfo(phaseOnly)
2071            #self.CloseFile()
2072            return
2073        elif histOnly: #====Histogram only CIF ================================
2074            print('Writing CIF output to file '+self.filename)
2075            #self.OpenFile()
2076            hist = histOnly
2077            #histname = histOnly.replace(' ','')
2078            oneblock = True
2079            self.quickmode = True
2080            self.ifHKLF = False
2081            self.ifPWDR = True
2082            self.Write(' ')
2083            self.Write(70*'#')
2084            #phasenam = self.Phases.keys()[0]
2085            WriteCIFitem(self.fp, 'data_'+self.CIFname)
2086            #print 'phasenam',phasenam
2087            #phaseblk = self.Phases[phasenam] # pointer to current phase info
2088            #instnam = instnam.replace(' ','')
2089            #WriteCIFitem(self.fp, '_pd_block_id',
2090            #             str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
2091            #             str(self.shortauthorname) + "|" + instnam + '|' + histname)
2092            #WriteAudit()
2093            #writeCIFtemplate(self.OverallParms['Controls'],'publ') # overall (publication) template
2094            #WriteOverall()
2095            #writeCIFtemplate(self.Phases[phasenam]['General'],'phase',phasenam) # write phase template
2096            # report the phase info
2097            #WritePhaseInfo(phasenam,hist)
2098            # preferred orientation
2099            #SH = FormatSH(phasenam)
2100            #MD = FormatHAPpo(phasenam)
2101            #if SH and MD:
2102            #    WriteCIFitem(self.fp, '_pd_proc_ls_pref_orient_corr', SH + '\n' + MD)
2103            #elif SH or MD:
2104            #    WriteCIFitem(self.fp, '_pd_proc_ls_pref_orient_corr', SH + MD)
2105            #else:
2106            #    WriteCIFitem(self.fp, '_pd_proc_ls_pref_orient_corr', 'none')
2107            # report profile, since one-block: include both histogram and phase info
2108            #WriteCIFitem(self.fp, '_pd_proc_ls_profile_function',
2109            #    FormatInstProfile(histblk["Instrument Parameters"],histblk['hId'])
2110            #        +'\n'+FormatPhaseProfile(phasenam))
2111            if hist.startswith("PWDR"):
2112                WritePowderData(hist)
2113            elif hist.startswith("HKLF"):
2114                WriteSingleXtalData(hist)
2115            #writeCIFtemplate(histblk,'powder',histblk['InstrName']) # write powder template
2116            #self.CloseFile()
2117            return
2118        #elif IncludeOnlyHist is not None: # truncate histogram list to only selected (for sequential export)
2119        #    self.Histograms = {IncludeOnlyHist:self.Histograms[IncludeOnlyHist]}
2120
2121        #===============================================================================
2122        # the export process for a full CIF starts here
2123        #===============================================================================
2124        self.InitExport(event)
2125        # load all of the tree into a set of dicts
2126        self.loadTree()
2127        # create a dict with refined values and their uncertainties
2128        self.loadParmDict()
2129        if self.ExportSelect('ask'): return
2130        if not self.filename:
2131            print('No name supplied')
2132            return
2133        self.OpenFile()
2134        #if self.ExportSelect('default'): return
2135        # Someday: get restraint & constraint info
2136        #restraintDict = self.OverallParms.get('Restraints',{})
2137        #for i in  self.OverallParms['Constraints']:
2138        #    print i
2139        #    for j in self.OverallParms['Constraints'][i]:
2140        #        print j
2141
2142        # is there anything to export?
2143        if len(self.Phases) == len(self.powderDict) == len(self.xtalDict) == 0:
2144           self.G2frame.ErrorDialog(
2145               'Empty project',
2146               'Project does not contain any data or phases. Are they interconnected?')
2147           return
2148        self.quickmode = False # full CIF
2149        phasenam = None # include all phases
2150        # Will this require a multiblock CIF?
2151        if len(self.Phases) > 1:
2152            oneblock = False
2153        elif len(self.powderDict) + len(self.xtalDict) > 1:
2154            oneblock = False
2155        else: # one phase, one dataset, Full CIF
2156            oneblock = True
2157
2158        # check there is an instrument name for every histogram
2159        self.ifPWDR = False
2160        self.ifHKLF = False
2161        invalid = 0
2162        key3 = 'InstrName'
2163        for hist in self.Histograms:
2164            if hist.startswith("PWDR"):
2165                self.ifPWDR = True
2166                key2 = "Sample Parameters"
2167                d = self.Histograms[hist][key2]
2168            elif hist.startswith("HKLF"):
2169                self.ifHKLF = True
2170                key2 = "Instrument Parameters"
2171                d = self.Histograms[hist][key2][0]
2172            instrname = d.get(key3)
2173            if instrname is None:
2174                d[key3] = ''
2175                invalid += 1
2176            elif instrname.strip() == '':
2177                invalid += 1
2178        if invalid:
2179            #msg = ""
2180            #if invalid > 3: msg = (
2181            #    "\n\nNote: it may be faster to set the name for\n"
2182            #    "one histogram for each instrument and use the\n"
2183            #    "File/Copy option to duplicate the name"
2184            #    )
2185            if not EditInstNames(): return
2186
2187        # check for a distance-angle range search range for each phase
2188        for phasenam in sorted(self.Phases.keys()):
2189            #i = self.Phases[phasenam]['pId']
2190            phasedict = self.Phases[phasenam] # pointer to current phase info
2191            if 'DisAglCtls' not in phasedict['General']:
2192                dlg = G2G.DisAglDialog(
2193                    self.G2frame,
2194                    {},
2195                    phasedict['General'])
2196                if dlg.ShowModal() == wx.ID_OK:
2197                    phasedict['General']['DisAglCtls'] = dlg.GetData()
2198                else:
2199                    dlg.Destroy()
2200                    return
2201                dlg.Destroy()
2202
2203        # check if temperature values & pressure are defaulted
2204        default = 0
2205        for hist in self.Histograms:
2206            if hist.startswith("PWDR"):
2207                key2 = "Sample Parameters"
2208                T = self.Histograms[hist][key2].get('Temperature')
2209                if not T:
2210                    default += 1
2211                elif T == 300:
2212                    default += 1
2213                P = self.Histograms[hist][key2].get('Pressure')
2214                if not P:
2215                    default += 1
2216                elif P == 1:
2217                    default += 1
2218        if default > 0:
2219            dlg = wx.MessageDialog(
2220                self.G2frame,
2221                '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?',
2222                'Check T and P values',
2223                wx.OK|wx.CANCEL)
2224            ret = dlg.ShowModal()
2225            dlg.Destroy()
2226            if ret != wx.ID_OK: return
2227        if oneblock:
2228            # select a dataset to use (there should only be one set in one block,
2229            # but take whatever comes 1st)
2230            for hist in self.Histograms:
2231                histblk = self.Histograms[hist]
2232                if hist.startswith("PWDR"):
2233                    instnam = histblk["Sample Parameters"]['InstrName']
2234                    break # ignore all but 1st data histogram
2235                elif hist.startswith("HKLF"):
2236                    instnam = histblk["Instrument Parameters"][0]['InstrName']
2237                    break # ignore all but 1st data histogram
2238        # give the user a window to edit CIF contents
2239        if not self.author:
2240            if not EditAuthor(): return
2241        self.ValidateAscii([('Author name',self.author),]) # check for ASCII strings where needed, warn on problems
2242        self.cifdefs = wx.Dialog(
2243            self.G2frame,
2244            style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
2245        EditCIFDefaults()
2246        self.cifdefs.CenterOnParent()
2247        if self.cifdefs.ShowModal() != wx.ID_OK:
2248            self.cifdefs.Destroy()
2249            return
2250        while self.ValidateAscii([('Author name',self.author),
2251                                  ]): # validate a few things as ASCII
2252            if self.cifdefs.ShowModal() != wx.ID_OK:
2253                self.cifdefs.Destroy()
2254                return
2255        self.cifdefs.Destroy()
2256        #======================================================================
2257        # Start writing the CIF - single block
2258        #======================================================================
2259        print('Writing CIF output to file '+self.filename+"...")
2260        #self.OpenFile()
2261        if self.currentExportType == 'single' or self.currentExportType == 'powder':
2262            #======Data only CIF (powder/xtal) ====================================
2263            hist = self.histnam[0]
2264            self.CIFname = hist[5:40].replace(' ','')
2265            WriteCIFitem(self.fp, 'data_'+self.CIFname)
2266            if hist.startswith("PWDR"):
2267                WritePowderData(hist)
2268            elif hist.startswith("HKLF"):
2269                WriteSingleXtalData(hist)
2270            else:
2271                print ("should not happen")
2272        elif oneblock:
2273            #====Single block, data & phase CIF ===================================
2274            WriteCIFitem(self.fp, 'data_'+self.CIFname)
2275            if phasenam is None: # if not already selected, select the first phase (should be one)
2276                phasenam = self.Phases.keys()[0]
2277            #print 'phasenam',phasenam
2278            #phaseblk = self.Phases[phasenam] # pointer to current phase info
2279            instnam = instnam.replace(' ','')
2280            WriteCIFitem(self.fp, '_pd_block_id',
2281                         str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
2282                         str(self.shortauthorname) + "|" + instnam)
2283            WriteAudit()
2284            writeCIFtemplate(self.OverallParms['Controls'],'publ') # overall (publication) template
2285            WriteOverall()
2286            writeCIFtemplate(self.Phases[phasenam]['General'],'phase',phasenam) # write phase template
2287            # report the phase info
2288            WritePhaseInfo(phasenam)
2289            if hist.startswith("PWDR"):
2290                # preferred orientation
2291                SH = FormatSH(phasenam)
2292                MD = FormatHAPpo(phasenam)
2293                if SH and MD:
2294                    WriteCIFitem(self.fp, '_pd_proc_ls_pref_orient_corr', SH + '\n' + MD)
2295                elif SH or MD:
2296                    WriteCIFitem(self.fp, '_pd_proc_ls_pref_orient_corr', SH + MD)
2297                else:
2298                    WriteCIFitem(self.fp, '_pd_proc_ls_pref_orient_corr', 'none')
2299                    # report profile, since one-block: include both histogram and phase info
2300                WriteCIFitem(self.fp, '_pd_proc_ls_profile_function',
2301                    FormatInstProfile(histblk["Instrument Parameters"],histblk['hId'])
2302                    +'\n'+FormatPhaseProfile(phasenam))
2303                histblk = self.Histograms[hist]["Sample Parameters"]
2304                writeCIFtemplate(histblk,'powder',histblk['InstrName']) # write powder template
2305                WritePowderData(hist)
2306            elif hist.startswith("HKLF"):
2307                histprm = self.Histograms[hist]["Instrument Parameters"][0]
2308                writeCIFtemplate(histprm,'single',histprm['InstrName']) # single crystal template
2309                WriteSingleXtalData(hist)
2310        else:
2311            #=== multiblock: multiple phases and/or histograms ====================
2312            nsteps = 1 + len(self.Phases) + len(self.powderDict) + len(self.xtalDict)
2313            dlg = wx.ProgressDialog('CIF progress','starting',nsteps,parent=self.G2frame)
2314#                Size = dlg.GetSize()
2315#                Size = (int(Size[0]*3),Size[1]) # increase size along x
2316#                dlg.SetSize(Size)
2317            dlg.CenterOnParent()
2318
2319            # publication info
2320            step = 1
2321            dlg.Update(step,"Exporting overall section")
2322            WriteCIFitem(self.fp, '\ndata_'+self.CIFname+'_publ')
2323            WriteAudit()
2324            WriteCIFitem(self.fp, '_pd_block_id',
2325                         str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
2326                         str(self.shortauthorname) + "|Overall")
2327            writeCIFtemplate(self.OverallParms['Controls'],'publ') #insert the publication template
2328            # ``template_publ.cif`` or a modified version
2329            # overall info
2330            WriteCIFitem(self.fp, 'data_'+str(self.CIFname)+'_overall')
2331            WriteOverall()
2332            #============================================================
2333            WriteCIFitem(self.fp, '# POINTERS TO PHASE AND HISTOGRAM BLOCKS')
2334            datablockidDict = {} # save block names here -- N.B. check for conflicts between phase & hist names (unlikely!)
2335            # loop over phase blocks
2336            if len(self.Phases) > 1:
2337                loopprefix = ''
2338                WriteCIFitem(self.fp, 'loop_   _pd_phase_block_id')
2339            else:
2340                loopprefix = '_pd_phase_block_id'
2341
2342            for phasenam in sorted(self.Phases.keys()):
2343                i = self.Phases[phasenam]['pId']
2344                datablockidDict[phasenam] = (str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
2345                             'phase_'+ str(i) + '|' + str(self.shortauthorname))
2346                WriteCIFitem(self.fp, loopprefix,datablockidDict[phasenam])
2347            # loop over data blocks
2348            if len(self.powderDict) + len(self.xtalDict) > 1:
2349                loopprefix = ''
2350                WriteCIFitem(self.fp, 'loop_   _pd_block_diffractogram_id')
2351            else:
2352                loopprefix = '_pd_block_diffractogram_id'
2353            for i in sorted(self.powderDict.keys()):
2354                hist = self.powderDict[i]
2355                histblk = self.Histograms[hist]
2356                instnam = histblk["Sample Parameters"]['InstrName']
2357                instnam = instnam.replace(' ','')
2358                j = histblk['hId']
2359                datablockidDict[hist] = (str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
2360                                         str(self.shortauthorname) + "|" +
2361                                         instnam + "_hist_"+str(j))
2362                WriteCIFitem(self.fp, loopprefix,datablockidDict[hist])
2363            for i in sorted(self.xtalDict.keys()):
2364                hist = self.xtalDict[i]
2365                histblk = self.Histograms[hist]
2366                instnam = histblk["Instrument Parameters"][0]['InstrName']
2367                instnam = instnam.replace(' ','')
2368                i = histblk['hId']
2369                datablockidDict[hist] = (str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
2370                                         str(self.shortauthorname) + "|" +
2371                                         instnam + "_hist_"+str(i))
2372                WriteCIFitem(self.fp, loopprefix,datablockidDict[hist])
2373            #============================================================
2374            # loop over phases, exporting them
2375            phasebyhistDict = {} # create a cross-reference to phases by histogram
2376            for j,phasenam in enumerate(sorted(self.Phases.keys())):
2377                step += 1
2378                dlg.Update(step,"Exporting phase "+phasenam+' (#'+str(j+1)+')')
2379                i = self.Phases[phasenam]['pId']
2380                WriteCIFitem(self.fp, '\ndata_'+self.CIFname+"_phase_"+str(i))
2381                WriteCIFitem(self.fp, '# Information for phase '+str(i))
2382                WriteCIFitem(self.fp, '_pd_block_id',datablockidDict[phasenam])
2383                # report the phase
2384                writeCIFtemplate(self.Phases[phasenam]['General'],'phase',phasenam) # write phase template
2385                WritePhaseInfo(phasenam)
2386                # preferred orientation
2387                if self.ifPWDR:
2388                    SH = FormatSH(phasenam)
2389                    MD = FormatHAPpo(phasenam)
2390                    if SH and MD:
2391                        WriteCIFitem(self.fp, '_pd_proc_ls_pref_orient_corr', SH + '\n' + MD)
2392                    elif SH or MD:
2393                        WriteCIFitem(self.fp, '_pd_proc_ls_pref_orient_corr', SH + MD)
2394                    else:
2395                        WriteCIFitem(self.fp, '_pd_proc_ls_pref_orient_corr', 'none')
2396                # report sample profile terms
2397                PP = FormatPhaseProfile(phasenam)
2398                if PP:
2399                    WriteCIFitem(self.fp, '_pd_proc_ls_profile_function',PP)
2400
2401            #============================================================
2402            # loop over histograms, exporting them
2403            for i in sorted(self.powderDict.keys()):
2404                hist = self.powderDict[i]
2405                histblk = self.Histograms[hist]
2406                if hist.startswith("PWDR"):
2407                    step += 1
2408                    dlg.Update(step,"Exporting "+hist.strip())
2409                    WriteCIFitem(self.fp, '\ndata_'+self.CIFname+"_pwd_"+str(i))
2410                    #instnam = histblk["Sample Parameters"]['InstrName']
2411                    # report instrumental profile terms
2412                    WriteCIFitem(self.fp, '_pd_proc_ls_profile_function',
2413                        FormatInstProfile(histblk["Instrument Parameters"],histblk['hId']))
2414                    WriteCIFitem(self.fp, '# Information for histogram '+str(i)+': '+hist)
2415                    WriteCIFitem(self.fp, '_pd_block_id',datablockidDict[hist])
2416                    histprm = self.Histograms[hist]["Sample Parameters"]
2417                    writeCIFtemplate(histprm,'powder',histprm['InstrName']) # powder template
2418                    WritePowderData(hist)
2419            for i in sorted(self.xtalDict.keys()):
2420                hist = self.xtalDict[i]
2421                histblk = self.Histograms[hist]
2422                if hist.startswith("HKLF"):
2423                    step += 1
2424                    dlg.Update(step,"Exporting "+hist.strip())
2425                    WriteCIFitem(self.fp, '\ndata_'+self.CIFname+"_sx_"+str(i))
2426                    #instnam = histblk["Instrument Parameters"][0]['InstrName']
2427                    WriteCIFitem(self.fp, '# Information for histogram '+str(i)+': '+hist)
2428                    WriteCIFitem(self.fp, '_pd_block_id',datablockidDict[hist])
2429                    histprm = self.Histograms[hist]["Instrument Parameters"][0]
2430                    writeCIFtemplate(histprm,'single',histprm['InstrName']) # single crystal template
2431                    WriteSingleXtalData(hist)
2432
2433            dlg.Destroy()
2434
2435        WriteCIFitem(self.fp, '#--' + 15*'eof--' + '#')
2436        #self.CloseFile()
2437        print("...export completed")
2438        print('file '+self.fullpath)
2439        # end of CIF export
2440
2441class ExportProjectCIF(ExportCIF):
2442    '''Used to create a CIF of an entire project
2443
2444    :param wx.Frame G2frame: reference to main GSAS-II frame
2445    '''
2446    def __init__(self,G2frame):
2447        ExportCIF.__init__(self,
2448            G2frame=G2frame,
2449            formatName = 'Full CIF',
2450            extension='.cif',
2451            longFormatName = 'Export project as CIF'
2452            )
2453        self.exporttype = ['project']
2454
2455    def Exporter(self,event=None):
2456        self._Exporter(event=event)
2457        self.CloseFile()
2458
2459    # def Writer(self,hist,mode='w'):
2460    #     '''Used for full project CIF export of a sequential fit.
2461    #     TODO: Needs extensive work
2462    #     '''
2463    #     # set the project file name
2464    #     self.CIFname = os.path.splitext(
2465    #         os.path.split(self.G2frame.GSASprojectfile)[1]
2466    #         )[0]+'_'+hist
2467    #     self.CIFname = self.CIFname.replace(' ','')
2468    #     self.OpenFile(mode=mode)
2469    #     self._Exporter(IncludeOnlyHist=hist)
2470    #     if mode == 'w':
2471    #         print('CIF written to file '+self.fullpath)
2472    #     self.CloseFile()
2473
2474class ExportPhaseCIF(ExportCIF):
2475    '''Used to create a simple CIF with one phase. Uses exact same code as
2476    :class:`ExportCIF` except that `phaseOnly` is set for the Exporter
2477    Shows up in menu as Quick CIF.
2478
2479    :param wx.Frame G2frame: reference to main GSAS-II frame
2480    '''
2481    def __init__(self,G2frame):
2482        ExportCIF.__init__(self,
2483            G2frame=G2frame,
2484            formatName = 'Quick CIF',
2485            extension='.cif',
2486            longFormatName = 'Export one phase in CIF'
2487            )
2488        self.exporttype = ['phase']
2489        # CIF-specific items
2490        self.author = ''
2491
2492    def Exporter(self,event=None):
2493        # get a phase and file name
2494        # the export process starts here
2495        self.InitExport(event)
2496        # load all of the tree into a set of dicts
2497        self.loadTree()
2498        # create a dict with refined values and their uncertainties
2499        self.loadParmDict()
2500        self.multiple = False
2501        self.currentExportType = 'phase'
2502        if self.ExportSelect('ask'): return
2503        self.OpenFile()
2504        self._Exporter(event=event,phaseOnly=self.phasenam[0])
2505        self.CloseFile()
2506
2507    def Writer(self,hist,phasenam,mode='w'):
2508        # set the project file name
2509        self.CIFname = os.path.splitext(
2510            os.path.split(self.G2frame.GSASprojectfile)[1]
2511            )[0]+'_'+phasenam+'_'+hist
2512        self.CIFname = self.CIFname.replace(' ','')
2513        self.OpenFile(mode=mode)
2514        self._Exporter(phaseOnly=phasenam)
2515        self.CloseFile()
2516
2517class ExportPwdrCIF(ExportCIF):
2518    '''Used to create a simple CIF containing diffraction data only. Uses exact same code as
2519    :class:`ExportCIF` except that `histOnly` is set for the Exporter
2520    Shows up in menu as Quick CIF.
2521
2522    :param wx.Frame G2frame: reference to main GSAS-II frame
2523    '''
2524    def __init__(self,G2frame):
2525        ExportCIF.__init__(self,
2526            G2frame=G2frame,
2527            formatName = 'Data-only CIF',
2528            extension='.cif',
2529            longFormatName = 'Export data as CIF'
2530            )
2531        if G2frame is None: raise AttributeError('CIF export requires data tree') # prevent use from Scriptable
2532        self.exporttype = ['powder']
2533        # CIF-specific items
2534        self.author = ''
2535
2536    def Exporter(self,event=None):
2537        self.InitExport(event)
2538        # load all of the tree into a set of dicts
2539        self.currentExportType = None
2540        self.loadTree()
2541        self.currentExportType = 'powder'
2542        # create a dict with refined values and their uncertainties
2543        self.loadParmDict()
2544        self.multiple = False
2545        if self.ExportSelect( # set export parameters
2546            AskFile='ask' # get a file name/directory to save in
2547            ): return
2548        self.OpenFile()
2549        self._Exporter(event=event,histOnly=self.histnam[0])
2550
2551    def Writer(self,hist,mode='w'):
2552        '''Used for histogram CIF export of a sequential fit.
2553        '''
2554        # set the project file name
2555        self.CIFname = os.path.splitext(
2556            os.path.split(self.G2frame.GSASprojectfile)[1]
2557            )[0]+'_'+hist
2558        self.CIFname = self.CIFname.replace(' ','')
2559        self.OpenFile(mode=mode)
2560        self._Exporter(histOnly=hist)
2561        if mode == 'w':
2562            print('CIF written to file '+self.fullpath)
2563        self.CloseFile()
2564
2565class ExportHKLCIF(ExportCIF):
2566    '''Used to create a simple CIF containing diffraction data only. Uses exact same code as
2567    :class:`ExportCIF` except that `histOnly` is set for the Exporter
2568    Shows up in menu as Quick CIF.
2569
2570    :param wx.Frame G2frame: reference to main GSAS-II frame
2571    '''
2572    def __init__(self,G2frame):
2573        ExportCIF.__init__(self,
2574            G2frame=G2frame,
2575            formatName = 'Data-only CIF',
2576            extension='.cif',
2577            longFormatName = 'Export data as CIF'
2578            )
2579        self.exporttype = ['single']
2580        # CIF-specific items
2581        self.author = ''
2582
2583    def Exporter(self,event=None):
2584        self.InitExport(event)
2585        # load all of the tree into a set of dicts
2586        self.currentExportType = None
2587        self.loadTree()
2588        self.currentExportType = 'single'
2589        # create a dict with refined values and their uncertainties
2590        self.loadParmDict()
2591        self.multiple = False
2592        if self.ExportSelect( # set export parameters
2593            AskFile='ask' # get a file name/directory to save in
2594            ): return
2595        self.OpenFile()
2596        self._Exporter(event=event,histOnly=self.histnam[0])
2597
2598#===============================================================================
2599# misc CIF utilities
2600#===============================================================================
2601def PickleCIFdict(fil):
2602    '''Loads a CIF dictionary, cherry picks out the items needed
2603    by local code and sticks them into a python dict and writes
2604    that dict out as a pickle file for later reuse.
2605    If the write fails a warning message is printed,
2606    but no exception occurs.
2607
2608    :param str fil: file name of CIF dictionary, will usually end
2609      in .dic
2610    :returns: the dict with the definitions
2611    '''
2612    import CifFile as cif # PyCifRW from James Hester
2613    cifdic = {}
2614    try:
2615        fp = open(fil,'r')             # patch: open file to avoid windows bug
2616        dictobj = cif.CifDic(fp)
2617        fp.close()
2618    except IOError:
2619        dictobj = cif.CifDic(fil)
2620    if DEBUG: print('loaded '+fil)
2621    for item in dictobj.keys():
2622        cifdic[item] = {}
2623        for j in (
2624            '_definition','_type',
2625            '_enumeration',
2626            '_enumeration_detail',
2627            '_enumeration_range'):
2628            if dictobj[item].get(j):
2629                cifdic[item][j] = dictobj[item][j]
2630    try:
2631        fil = os.path.splitext(fil)[0]+'.cpickle'
2632        fp = open(fil,'w')
2633        pickle.dump(cifdic,fp)
2634        fp.close()
2635        if DEBUG: print('wrote '+fil)
2636    except:
2637        print ('Unable to write '+fil)
2638    return cifdic
2639
2640def LoadCIFdic():
2641    '''Create a composite core+powder CIF lookup dict containing
2642    information about all items in the CIF dictionaries, loading
2643    pickled files if possible. The routine looks for files
2644    named cif_core.cpickle and cif_pd.cpickle in every
2645    directory in the path and if they are not found, files
2646    cif_core.dic and/or cif_pd.dic are read.
2647
2648    :returns: the dict with the definitions
2649    '''
2650    cifdic = {}
2651    for ftyp in "cif_core","cif_pd":
2652        for loc in sys.path:
2653            fil = os.path.join(loc,ftyp+".cpickle")
2654            if not os.path.exists(fil): continue
2655            fp = open(fil,'r')
2656            try:
2657                cifdic.update(pickle.load(fp))
2658                if DEBUG: print('reloaded '+fil)
2659                break
2660            finally:
2661                fp.close()
2662        else:
2663            for loc in sys.path:
2664                fil = os.path.join(loc,ftyp+".dic")
2665                if not os.path.exists(fil): continue
2666                #try:
2667                if True:
2668                    cifdic.update(PickleCIFdict(fil))
2669                    break
2670                #except:
2671                #    pass
2672            else:
2673                print('Could not load '+ftyp+' dictionary')
2674    return cifdic
2675
2676class CIFdefHelp(wx.Button):
2677    '''Create a help button that displays help information on
2678    the current data item
2679
2680    :param parent: the panel which will be the parent of the button
2681    :param str msg: the help text to be displayed
2682    :param wx.Dialog helpwin: Frame for CIF editing dialog
2683    :param wx.TextCtrl helptxt: TextCtrl where help text is placed
2684    '''
2685    def __init__(self,parent,msg,helpwin,helptxt):
2686        wx.Button.__init__(self,parent,wx.ID_HELP)
2687        self.Bind(wx.EVT_BUTTON,self._onPress)
2688        self.msg=msg
2689        self.parent = parent
2690        #self.helpwin = self.parent.helpwin
2691        self.helpwin = helpwin
2692        self.helptxt = helptxt
2693    def _onPress(self,event):
2694        'Respond to a button press by displaying the requested text'
2695        try:
2696            #helptxt = self.helptxt
2697            ow,oh = self.helptxt.GetSize()
2698            self.helptxt.SetLabel(self.msg)
2699            w,h = self.helptxt.GetSize()
2700            if h > oh:
2701                self.helpwin.GetSizer().Fit(self.helpwin)
2702        except: # error posting help, ignore
2703            return
2704
2705def CIF2dict(cf):
2706    '''copy the contents of a CIF out from a PyCifRW block object
2707    into a dict
2708
2709    :returns: cifblk, loopstructure where cifblk is a dict with
2710      CIF items and loopstructure is a list of lists that defines
2711      which items are in which loops.
2712    '''
2713    blk = cf.keys()[0] # assume templates are a single CIF block, use the 1st
2714    loopstructure = cf[blk].loopnames()[:] # copy over the list of loop contents
2715    dblk = {}
2716    for item in cf[blk].keys(): # make a copy of all the items in the block
2717        dblk[item] = cf[blk][item]
2718    return dblk,loopstructure
2719
2720def dict2CIF(dblk,loopstructure,blockname='Template'):
2721    '''Create a PyCifRW CIF object containing a single CIF
2722    block object from a dict and loop structure list.
2723
2724    :param dblk: a dict containing values for each CIF item
2725    :param list loopstructure: a list of lists containing the contents of
2726      each loop, as an example::
2727
2728         [ ["_a","_b"], ["_c"], ["_d_1","_d_2","_d_3"]]
2729
2730      this describes a CIF with this type of structure::
2731
2732        loop_ _a _b <a1> <b1> <a2> ...
2733        loop_ _c <c1> <c2>...
2734        loop _d_1 _d_2 _d_3 ...
2735
2736      Note that the values for each looped CIF item, such as _a,
2737      are contained in a list, for example as cifblk["_a"]
2738
2739    :param str blockname: an optional name for the CIF block.
2740      Defaults to 'Template'
2741
2742    :returns: the newly created PyCifRW CIF object
2743    '''
2744
2745    import CifFile as cif # PyCifRW from James Hester
2746    # compile a 'list' of items in loops
2747    loopnames = set()
2748    for i in loopstructure:
2749        loopnames |= set(i)
2750    # create a new block
2751    newblk = cif.CifBlock()
2752    # add the looped items
2753    for keys in loopstructure:
2754        vals = []
2755        for key in keys:
2756            vals.append(dblk[key])
2757        newblk.AddCifItem(([keys],[vals]))
2758    # add the non-looped items
2759    for item in dblk:
2760        if item in loopnames: continue
2761        newblk[item] = dblk[item]
2762    # create a CIF and add the block
2763    newcf = cif.CifFile()
2764    newcf[blockname] = newblk
2765    return newcf
2766
2767
2768class EditCIFtemplate(wx.Dialog):
2769    '''Create a dialog for editing a CIF template. The edited information is
2770    placed in cifblk. If the CIF is saved as a file, the name of that file
2771    is saved as ``self.newfile``.
2772
2773    :param wx.Frame parent: parent frame or None
2774    :param cifblk: dict or PyCifRW block containing values for each CIF item
2775    :param list loopstructure: a list of lists containing the contents of
2776      each loop, as an example::
2777
2778         [ ["_a","_b"], ["_c"], ["_d_1","_d_2","_d_3"]]
2779
2780      this describes a CIF with this type of structure::
2781
2782        loop_ _a _b <a1> <b1> <a2> ...
2783        loop_ _c <c1> <c2>...
2784        loop _d_1 _d_2 _d_3 ...
2785
2786      Note that the values for each looped CIF item, such as _a,
2787      are contained in a list, for example as cifblk["_a"]
2788
2789    :param str defaultname: specifies the default file name to be used for
2790      saving the CIF.
2791    '''
2792    def __init__(self,parent,cifblk,loopstructure,defaultname):
2793        OKbuttons = []
2794        self.cifblk = cifblk
2795        self.loopstructure = loopstructure
2796        self.newfile = None
2797        self.defaultname = defaultname
2798        global CIFdic  # once this is loaded, keep it around
2799        if CIFdic is None:
2800            CIFdic = LoadCIFdic()
2801        wx.Dialog.__init__(self,parent,style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
2802
2803        # define widgets that will be needed during panel creation
2804        self.helptxt = wx.StaticText(self,wx.ID_ANY,"")
2805        savebtn = wx.Button(self, wx.ID_CLOSE, "Save as template")
2806        OKbuttons.append(savebtn)
2807        savebtn.Bind(wx.EVT_BUTTON,self._onSave)
2808        OKbtn = wx.Button(self, wx.ID_OK, "Use")
2809        OKbtn.SetDefault()
2810        OKbuttons.append(OKbtn)
2811
2812        self.SetTitle('Edit items in CIF template')
2813        vbox = wx.BoxSizer(wx.VERTICAL)
2814        cpnl = EditCIFpanel(self,cifblk,loopstructure,CIFdic,OKbuttons,size=(300,300))
2815        vbox.Add(cpnl, 1, wx.ALIGN_LEFT|wx.ALL|wx.EXPAND, 0)
2816        G2G.HorizontalLine(vbox,self)
2817        vbox.Add(self.helptxt, 0, wx.EXPAND|wx.ALL, 5)
2818        G2G.HorizontalLine(vbox,self)
2819        btnsizer = wx.BoxSizer(wx.HORIZONTAL)
2820        btn = wx.Button(self, wx.ID_CANCEL)
2821        btnsizer.Add(btn,0,wx.ALIGN_CENTER|wx.ALL)
2822        btnsizer.Add(savebtn,0,wx.ALIGN_CENTER|wx.ALL)
2823        btnsizer.Add(OKbtn,0,wx.ALIGN_CENTER|wx.ALL)
2824        vbox.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5)
2825        self.SetSizer(vbox)
2826        vbox.Fit(self)
2827    def Post(self):
2828        '''Display the dialog
2829
2830        :returns: True unless Cancel has been pressed.
2831        '''
2832        return (self.ShowModal() == wx.ID_OK)
2833    def _onSave(self,event):
2834        'Save CIF entries in a template file'
2835        pth = G2G.GetExportPath(self.G2frame)
2836        dlg = wx.FileDialog(
2837            self, message="Save as CIF template",
2838            defaultDir=pth,
2839            defaultFile=self.defaultname,
2840            wildcard="CIF (*.cif)|*.cif",
2841            style=wx.SAVE)
2842        val = (dlg.ShowModal() == wx.ID_OK)
2843        fil = dlg.GetPath()
2844        dlg.Destroy()
2845        if val: # ignore a Cancel button
2846            fil = os.path.splitext(fil)[0]+'.cif' # force extension
2847            fp = open(fil,'w')
2848            newcf = dict2CIF(self.cifblk,self.loopstructure)
2849            fp.write(newcf.WriteOut())
2850            fp.close()
2851            self.newfile = fil
2852            self.EndModal(wx.ID_OK)
2853
2854class EditCIFpanel(wxscroll.ScrolledPanel):
2855    '''Creates a scrolled panel for editing CIF template items
2856
2857    :param wx.Frame parent: parent frame where panel will be placed
2858    :param cifblk: dict or PyCifRW block containing values for each CIF item
2859    :param list loopstructure: a list of lists containing the contents of
2860      each loop, as an example::
2861
2862         [ ["_a","_b"], ["_c"], ["_d_1","_d_2","_d_3"]]
2863
2864      this describes a CIF with this type of structure::
2865
2866        loop_ _a _b <a1> <b1> <a2> ...
2867        loop_ _c <c1> <c2>...
2868        loop _d_1 _d_2 _d_3 ...
2869
2870      Note that the values for each looped CIF item, such as _a,
2871      are contained in a list, for example as cifblk["_a"]
2872
2873    :param dict cifdic: optional CIF dictionary definitions
2874    :param list OKbuttons: A list of wx.Button objects that should
2875      be disabled when information in the CIF is invalid
2876    :param (other): optional keyword parameters for wx.ScrolledPanel
2877    '''
2878    def __init__(self, parent, cifblk, loopstructure, cifdic={}, OKbuttons=[], **kw):
2879        self.parent = parent
2880        wxscroll.ScrolledPanel.__init__(self, parent, wx.ID_ANY, **kw)
2881        self.vbox = None
2882        self.AddDict = None
2883        self.cifdic = cifdic
2884        self.cifblk = cifblk
2885        self.loops = loopstructure
2886        self.parent = parent
2887        self.LayoutCalled = False
2888        self.parentOKbuttons = OKbuttons
2889        self.ValidatedControlsList = []
2890        self._fill()
2891    def _fill(self):
2892        'Fill the scrolled panel with widgets for each CIF item'
2893        wx.BeginBusyCursor()
2894        self.AddDict = {}
2895        self.ValidatedControlsList = []
2896        # delete any only contents
2897        if self.vbox:
2898            if 'phoenix' in wx.version():
2899                self.vbox.Clear(True)
2900            else:
2901                self.vbox.DeleteWindows()
2902            self.vbox = None
2903            self.Update()
2904        vbox = wx.BoxSizer(wx.VERTICAL)
2905        self.vbox = vbox
2906        # compile a 'list' of items in loops
2907        loopnames = set()
2908        for i in self.loops:
2909            loopnames |= set(i)
2910        # post the looped CIF items
2911        for lnum,lp in enumerate(self.loops):
2912            hbox = wx.BoxSizer(wx.HORIZONTAL)
2913            hbox.Add(wx.StaticText(self,wx.ID_ANY,'Loop '+str(lnum+1)))
2914            vbox.Add(hbox)
2915            but = wx.Button(self,wx.ID_ANY,"Add row")
2916            self.AddDict[but]=lnum
2917
2918            hbox.Add(but)
2919            but.Bind(wx.EVT_BUTTON,self.OnAddRow)
2920            fbox = wx.GridBagSizer(0, 0)
2921            vbox.Add(fbox)
2922            rows = 0
2923            for i,item in enumerate(lp):
2924                txt = wx.StaticText(self,wx.ID_ANY,item+"  ")
2925                fbox.Add(txt,(0,i+1))
2926                # if self.cifdic.get(item):
2927                #     df = self.cifdic[item].get('_definition')
2928                #     if df:
2929                #         txt.SetToolTipString(G2IO.trim(df))
2930                #         but = CIFdefHelp(self,
2931                #                          "Definition for "+item+":\n\n"+G2IO.trim(df),
2932                #                          self.parent,
2933                #                          self.parent.helptxt)
2934                #         fbox.Add(but,(1,i+1),flag=wx.ALIGN_CENTER)
2935                for j,val in enumerate(self.cifblk[item]):
2936                    ent = self.CIFEntryWidget(self.cifblk[item],j,item)
2937                    #fbox.Add(ent,(j+2,i+1),flag=wx.EXPAND|wx.ALL)
2938                    fbox.Add(ent,(j+1,i+1),flag=wx.EXPAND|wx.ALL)
2939                if self.cifdic.get(item):
2940                    df = self.cifdic[item].get('_definition')
2941                    if df:
2942                        txt.SetToolTipString(G2IO.trim(df))
2943                        but = CIFdefHelp(self,
2944                                         "Definition for "+item+":\n\n"+G2IO.trim(df),
2945                                         self.parent,
2946                                         self.parent.helptxt)
2947                        fbox.Add(but,(j+2,i+1),flag=wx.ALIGN_CENTER)
2948                rows = max(rows,len(self.cifblk[item]))
2949            for i in range(rows):
2950                txt = wx.StaticText(self,wx.ID_ANY,str(i+1))
2951                fbox.Add(txt,(i+2,0))
2952            line = wx.StaticLine(self,wx.ID_ANY, size=(-1,3), style=wx.LI_HORIZONTAL)
2953            vbox.Add(line, 0, wx.EXPAND|wx.ALIGN_CENTER|wx.ALL, 10)
2954
2955        # post the non-looped CIF items
2956        for item in sorted(self.cifblk.keys()):
2957            if item not in loopnames:
2958                hbox = wx.BoxSizer(wx.HORIZONTAL)
2959                vbox.Add(hbox)
2960                txt = wx.StaticText(self,wx.ID_ANY,item)
2961                hbox.Add(txt)
2962                ent = self.CIFEntryWidget(self.cifblk,item,item)
2963                hbox.Add(ent)
2964                if self.cifdic.get(item):
2965                    df = self.cifdic[item].get('_definition')
2966                    if df:
2967                        txt.SetToolTipString(G2IO.trim(df))
2968                        but = CIFdefHelp(self,
2969                                         "Definition for "+item+":\n\n"+G2IO.trim(df),
2970                                         self.parent,
2971                                         self.parent.helptxt)
2972                        hbox.Add(but,0,wx.ALL,2)
2973        self.SetSizer(vbox)
2974        #vbox.Fit(self.parent)
2975        self.SetAutoLayout(1)
2976        self.SetupScrolling()
2977        self.Bind(rw.EVT_RW_LAYOUT_NEEDED, self.OnLayoutNeeded)
2978        self.Layout()
2979        wx.EndBusyCursor()
2980    def OnLayoutNeeded(self,event):
2981        '''Called when an update of the panel layout is needed. Calls
2982        self.DoLayout after the current operations are complete using
2983        CallAfter. This is called only once, according to flag
2984        self.LayoutCalled, which is cleared in self.DoLayout.
2985        '''
2986        if self.LayoutCalled: return # call already queued
2987        wx.CallAfter(self.DoLayout) # queue a call
2988        self.LayoutCalled = True
2989    def DoLayout(self):
2990        '''Update the Layout and scroll bars for the Panel. Clears
2991        self.LayoutCalled so that next change to panel can
2992        request a new update
2993        '''
2994        wx.BeginBusyCursor()
2995        self.Layout()
2996        self.SetupScrolling()
2997        wx.EndBusyCursor()
2998        self.LayoutCalled = False
2999    def OnAddRow(self,event):
3000        'add a row to a loop'
3001        lnum = self.AddDict.get(event.GetEventObject())
3002        if lnum is None: return
3003        for item in self.loops[lnum]:
3004            self.cifblk[item].append('?')
3005        self._fill()
3006
3007    def ControlOKButton(self,setvalue):
3008        '''Enable or Disable the OK button(s) for the dialog. Note that this is
3009        passed into the ValidatedTxtCtrl for use by validators.
3010
3011        :param bool setvalue: if True, all entries in the dialog are
3012          checked for validity. The first invalid control triggers
3013          disabling of buttons.
3014          If False then the OK button(s) are disabled with no checking
3015          of the invalid flag for each control.
3016        '''
3017        if setvalue: # turn button on, do only if all controls show as valid
3018            for ctrl in self.ValidatedControlsList:
3019                if ctrl.invalid:
3020                    for btn in self.parentOKbuttons:
3021                        btn.Disable()
3022                    return
3023            else:
3024                for btn in self.parentOKbuttons:
3025                    btn.Enable()
3026        else:
3027            for btn in self.parentOKbuttons:
3028                btn.Disable()
3029
3030    def CIFEntryWidget(self,dct,item,dataname):
3031        '''Create an entry widget for a CIF item. Use a validated entry for numb values
3032        where int is required when limits are integers and floats otherwise.
3033        At present this does not allow entry of the special CIF values of "." and "?" for
3034        numerical values and highlights them as invalid.
3035        Use a selection widget when there are specific enumerated values for a string.
3036        '''
3037        if self.cifdic.get(dataname):
3038            if self.cifdic[dataname].get('_enumeration'):
3039                values = ['?']+self.cifdic[dataname]['_enumeration']
3040                choices = ['undefined']
3041                for i in self.cifdic[dataname].get('_enumeration_detail',values):
3042                    choices.append(G2IO.trim(i))
3043                ent = G2G.EnumSelector(self, dct, item, choices, values, size=(200, -1))
3044                return ent
3045            if self.cifdic[dataname].get('_type') == 'numb':
3046                mn = None
3047                mx = None
3048                hint = int
3049                if self.cifdic[dataname].get('_enumeration_range'):
3050                    rng = self.cifdic[dataname]['_enumeration_range'].split(':')
3051                    if '.' in rng[0] or '.' in rng[1]: hint = float
3052                    if rng[0]: mn = hint(rng[0])
3053                    if rng[1]: mx = hint(rng[1])
3054                    ent = G2G.ValidatedTxtCtrl(
3055                        self,dct,item,typeHint=hint,min=mn,max=mx,
3056                        CIFinput=True,ASCIIonly=True,
3057                        OKcontrol=self.ControlOKButton)
3058                    self.ValidatedControlsList.append(ent)
3059                    return ent
3060        rw1 = rw.ResizeWidget(self)
3061        ent = G2G.ValidatedTxtCtrl(
3062            rw1,dct,item,size=(100, 20),
3063            style=wx.TE_MULTILINE|wx.TE_PROCESS_ENTER,
3064            CIFinput=True,ASCIIonly=True,
3065            OKcontrol=self.ControlOKButton)
3066        self.ValidatedControlsList.append(ent)
3067        return rw1
3068
3069class CIFtemplateSelect(wx.BoxSizer):
3070    '''Create a set of buttons to show, select and edit a CIF template
3071
3072    :param frame: wx.Frame object of parent
3073    :param panel: wx.Panel object where widgets should be placed
3074    :param str tmplate: one of 'publ', 'phase', or 'instrument' to determine
3075      the type of template
3076    :param dict G2dict: GSAS-II dict where CIF should be placed. The key
3077      "CIF_template" will be used to store either a list or a string.
3078      If a list, it will contain a dict and a list defining loops. If
3079      an str, it will contain a file name.
3080    :param function repaint: reference to a routine to be called to repaint
3081      the frame after a change has been made
3082    :param str title: A line of text to show at the top of the window
3083    :param str defaultname: specifies the default file name to be used for
3084      saving the CIF.
3085    '''
3086    def __init__(self,frame,panel,tmplate,G2dict, repaint, title, defaultname=''):
3087        wx.BoxSizer.__init__(self,wx.VERTICAL)
3088        self.cifdefs = frame
3089        self.dict = G2dict
3090        self.repaint = repaint
3091        templateDefName = 'template_'+tmplate+'.cif'
3092        self.CIF = G2dict.get("CIF_template")
3093        if defaultname:
3094            self.defaultname = G2obj.StripUnicode(defaultname)
3095            self.defaultname = re.sub(r'[^a-zA-Z0-9_-]','',self.defaultname)
3096            self.defaultname = tmplate + "_" + self.defaultname + ".cif"
3097        else:
3098            self.defaultname = ''
3099
3100        txt = wx.StaticText(panel,wx.ID_ANY,title)
3101        self.Add(txt,0,wx.ALIGN_CENTER)
3102        # change font on title
3103        txtfnt = txt.GetFont()
3104        txtfnt.SetWeight(wx.BOLD)
3105        txtfnt.SetPointSize(2+txtfnt.GetPointSize())
3106        txt.SetFont(txtfnt)
3107        self.Add((-1,3))
3108
3109        if not self.CIF: # empty or None
3110            for pth in [os.getcwd()]+sys.path:
3111                fil = os.path.join(pth,self.defaultname)
3112                if os.path.exists(fil) and self.defaultname:
3113                    self.CIF = fil
3114                    CIFtxt = "Template: "+self.defaultname
3115                    break
3116            else:
3117                for pth in sys.path:
3118                    fil = os.path.join(pth,templateDefName)
3119                    if os.path.exists(fil):
3120                        self.CIF = fil
3121                        CIFtxt = "Template: "+templateDefName
3122                        break
3123                else:
3124                    print("Default CIF template "+self.defaultname+' not found in path!')
3125                    self.CIF = None
3126                    CIFtxt = "none! (No template found)"
3127        elif type(self.CIF) is not list and type(self.CIF) is not tuple:
3128            if not os.path.exists(self.CIF):
3129                print("Error: template file has disappeared: "+self.CIF)
3130                self.CIF = None
3131                CIFtxt = "none! (file not found)"
3132            else:
3133                if len(self.CIF) < 50:
3134                    CIFtxt = "File: "+self.CIF
3135                else:
3136                    CIFtxt = "File: ..."+self.CIF[-50:]
3137        else:
3138            CIFtxt = "Template is customized"
3139        # show template source
3140        self.Add(wx.StaticText(panel,wx.ID_ANY,CIFtxt))
3141        # show str, button to select file; button to edit (if CIF defined)
3142        but = wx.Button(panel,wx.ID_ANY,"Select Template File")
3143        but.Bind(wx.EVT_BUTTON,self._onGetTemplateFile)
3144        hbox =  wx.BoxSizer(wx.HORIZONTAL)
3145        hbox.Add(but,0,0,2)
3146        but = wx.Button(panel,wx.ID_ANY,"Edit Template")
3147        but.Bind(wx.EVT_BUTTON,self._onEditTemplateContents)
3148        if self.CIF is None: but.Disable() # nothing to edit!
3149        hbox.Add(but,0,0,2)
3150        self.Add(hbox)
3151    def _onGetTemplateFile(self,event):
3152        'select a template file'
3153        pth = G2G.GetImportPath(self.G2frame)
3154        if not pth: pth = '.'
3155        dlg = wx.FileDialog(
3156            self.cifdefs, message="Read CIF template file",
3157            defaultDir=pth,
3158            defaultFile=self.defaultname,
3159            wildcard="CIF (*.cif)|*.cif",
3160            style=wx.OPEN)
3161        ret = dlg.ShowModal()
3162        fil = dlg.GetPath()
3163        dlg.Destroy()
3164        if ret == wx.ID_OK:
3165            cf = G2IO.ReadCIF(fil)
3166            if len(cf.keys()) == 0:
3167                raise Exception("No CIF data_ blocks found")
3168            if len(cf.keys()) != 1:
3169                raise Exception('Error, CIF Template has more than one block: '+fil)
3170            self.dict["CIF_template"] = fil
3171            self.repaint() #EditCIFDefaults()
3172
3173    def _onEditTemplateContents(self,event):
3174        'Called to edit the contents of a CIF template'
3175        if type(self.CIF) is list or  type(self.CIF) is tuple:
3176            dblk,loopstructure = copy.deepcopy(self.CIF) # don't modify original
3177        else:
3178            cf = G2IO.ReadCIF(self.CIF)
3179            dblk,loopstructure = CIF2dict(cf)
3180        dlg = EditCIFtemplate(self.cifdefs,dblk,loopstructure,self.defaultname)
3181        val = dlg.Post()
3182        if val:
3183            if dlg.newfile: # results saved in file
3184                self.dict["CIF_template"] = dlg.newfile
3185            else:
3186                self.dict["CIF_template"] = [dlg.cifblk,dlg.loopstructure]
3187            self.repaint() #EditCIFDefaults() # note that this does a dlg.Destroy()
3188        else:
3189            dlg.Destroy()
3190
3191#===============================================================================
3192# end of misc CIF utilities
3193#===============================================================================
Note: See TracBrowser for help on using the repository browser.