source: trunk/exports/G2export_CIF.py @ 3487

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

fix a phoenix/classic issue with SetChecked? vs SetCheckedItems? in G2ctrlGUI
Quick Cif now can export multiple phases - each data is named as phase name (not gpx name)

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