source: trunk/exports/G2export_CIF.py @ 3246

Last change on this file since 3246 was 3246, checked in by toby, 5 years ago

cif export: use sig for x,x,x positions etc; column order for seq table atom positions; misc Py3 fixes

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