source: trunk/exports/G2export_CIF.py @ 3200

Last change on this file since 3200 was 3200, checked in by vondreele, 4 years ago

fix bugs in various exporters for cif & pdb files.
allow for missing SGDataSGFixed? parameter in SGPrint

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