source: trunk/exports/G2export_CIF.py @ 3796

Last change on this file since 3796 was 3796, checked in by toby, 4 years ago

fix problem of empty distance/angle table

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