source: trunk/exports/G2export_CIF.py @ 3253

Last change on this file since 3253 was 3253, checked in by toby, 6 years ago

remove extraneous reload; improve scriptable docs

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