source: trunk/exports/G2export_CIF.py @ 4289

Last change on this file since 4289 was 4289, checked in by vondreele, 2 years ago

start on 'Compare' for comparison of bonding polyhedra between phases & within phases
include max shift/esd in cif output - works for Hessian refinement only

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