source: trunk/exports/G2export_CIF.py @ 3793

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

add density to CIF

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