source: trunk/exports/G2export_CIF.py @ 3491

Last change on this file since 3491 was 3491, checked in by vondreele, 5 years ago

fix export/import of magnetic cifs

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