source: trunk/exports/G2export_CIF.py @ 4361

Last change on this file since 4361 was 4361, checked in by toby, 23 months ago

fix hist CIF export, remove dev. ver of IntPDFtool

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