source: trunk/exports/G2export_CIF.py @ 5078

Last change on this file since 5078 was 5078, checked in by toby, 8 months ago

start on mmCIF export

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Author Revision URL Id
File size: 222.1 KB
Line 
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3########### SVN repository information ###################
4# $Date: 2021-11-13 17:41:21 +0000 (Sat, 13 Nov 2021) $
5# $Author: toby $
6# $Revision: 5078 $
7# $URL: trunk/exports/G2export_CIF.py $
8# $Id: G2export_CIF.py 5078 2021-11-13 17:41:21Z toby $
9########### SVN repository information ###################
10'''
11*Module G2export_CIF: CIF Exports*
12------------------------------------------------------
13
14This implements a complex exporter :class:`ExportCIF` that can implement an
15entire project in a complete CIF intended for submission as a
16publication. In addition, there are three subclasses of :class:`ExportCIF`:
17:class:`ExportProjectCIF`,
18:class:`ExportPhaseCIF` and :class:`ExportDataCIF` where extra parameters
19for the _Exporter() determine if a project, single phase or data set are written.
20'''
21
22from __future__ import division, print_function
23import platform
24import datetime as dt
25import os.path
26import sys
27import numpy as np
28if '2' in platform.python_version_tuple()[0]:
29    import cPickle as pickle
30else:
31    import pickle
32import copy
33import re
34try:
35    import wx
36    import wx.lib.scrolledpanel as wxscroll
37    import wx.lib.resizewidget as rw
38except ImportError:
39    # Avoid wx dependency for CLI
40    class Placeholder(object):
41        def __init__(self):
42            self.BoxSizer = object
43            self.Button = object
44            self.Dialog = object
45            self.ScrolledPanel = object
46    wx = Placeholder()
47    wxscroll = Placeholder()
48import GSASIIpath
49GSASIIpath.SetVersionNumber("$Revision: 5078 $")
50import GSASIIIO as G2IO
51try:
52    import GSASIIctrlGUI as G2G
53except ImportError:
54    pass
55import GSASIIobj as G2obj
56import GSASIImath as G2mth
57import GSASIIspc as G2spc
58import GSASIIlattice as G2lat
59import GSASIIstrMain as G2stMn
60import GSASIIstrIO as G2stIO       
61import GSASIImapvars as G2mv
62import GSASIIElem as G2el
63import GSASIIpy3 as G2py3
64
65DEBUG = False    #True to skip printing of reflection/powder profile lists
66
67CIFdic = None
68
69cellNames = ['length_a','length_b','length_c',
70             'angle_alpha','angle_beta ','angle_gamma',
71             'volume']
72def striphist(var,insChar=''):
73    'strip a histogram number from a var name'
74    sv = var.split(':')
75    if len(sv) <= 1: return var
76    if sv[1]:
77        sv[1] = insChar
78    return ':'.join(sv)
79
80def getCellwStrain(phasedict,seqData,pId,histname):
81    'Get cell parameters and their errors for a sequential fit'
82    #newCellDict = {}
83    #if name in seqData and 'newCellDict' in seqData[histname]:
84    #    newCellDict.update(seqData[histname]['newCellDict'])
85   
86    pfx = str(pId)+'::' # prefix for A values from phase
87    Albls = [pfx+'A'+str(i) for i in range(6)]
88    Avals = G2lat.cell2A(phasedict['General']['Cell'][1:7])
89    #AiLookup = {}
90    DijLookup = {}
91    zeroDict = dict(zip(Avals,6*[0.,]))
92    for i,v in enumerate(('D11','D22','D33','D12','D13','D23')):
93        if pfx+v in seqData[histname]['newCellDict']:
94            Avals[i] = seqData[histname]['newCellDict'][pfx+v][1]
95            #AiLookup[seqData[histname]['newCellDict'][pfx+v][0]] = pfx+v
96            DijLookup[pfx+v] = seqData[histname]['newCellDict'][pfx+v][0]
97    covData = {  # relabeled with p:h:Dij as p::Ai
98        'varyList': [DijLookup.get(striphist(v),v) for v in seqData[histname]['varyList']], 
99        'covMatrix': seqData[histname]['covMatrix']}
100    # apply symmetry
101    cellDict = dict(zip(Albls,Avals))
102    try:    # convert to direct cell
103        A,zeros = G2stIO.cellFill(pfx,phasedict['General']['SGData'],cellDict,zeroDict)
104        cell = list(G2lat.A2cell(A)) + [G2lat.calc_V(A)]
105        cE = G2stIO.getCellEsd(pfx,phasedict['General']['SGData'],A,covData)
106    except:
107        cell = 7*[None]
108        cE = 7*[None]
109    return cell,cE
110
111def mkSeqResTable(mode,seqHistList,seqData,Phases,Histograms,Controls):
112    '''Setup sequential results table (based on code from
113    GSASIIseqGUI.UpdateSeqResults)
114
115    TODO: This should be merged with the table build code in
116    GSASIIseqGUI.UpdateSeqResults and moved to somewhere non-GUI
117    like GSASIIstrIO to create a single routine that can be used
118    in both places, but this means returning some
119    of the code that has been removed from there
120    '''
121
122    newAtomDict = seqData[seqHistList[0]].get('newAtomDict',{}) # dict with atom positions; relative & absolute
123    atomLookup = {newAtomDict[item][0]:item for item in newAtomDict if item in seqData['varyList']}
124    phaseLookup = {Phases[phase]['pId']:phase for phase in Phases}
125
126    # make dict of varied cell parameters equivalents
127    ESDlookup = {} # provides the Dij term for each Ak term (where terms are refined)
128    Dlookup = {} # provides the Ak term for each Dij term (where terms are refined)
129    newCellDict = {}
130    for name in seqHistList:
131        if name in seqData and 'newCellDict' in seqData[name]:
132            newCellDict.update(seqData[name]['newCellDict'])
133    cellAlist = []
134    for item in newCellDict:
135        cellAlist.append(newCellDict[item][0])
136        if item in seqData.get('varyList',[]):
137            ESDlookup[newCellDict[item][0]] = item
138            Dlookup[item] = newCellDict[item][0]
139    # add coordinate equivalents to lookup table
140    for parm in atomLookup:
141        Dlookup[atomLookup[parm]] = parm
142        ESDlookup[parm] = atomLookup[parm]
143
144    # get unit cell & symmetry for all phases & initial stuff for later use
145    RecpCellTerms = {}
146    SGdata = {}
147    uniqCellIndx = {}
148    #initialCell = {}
149    RcellLbls = {}
150    zeroDict = {}
151    for phase in Phases:
152        pId = Phases[phase]['pId']
153        pfx = str(pId)+'::' # prefix for A values from phase
154        RcellLbls[pId] = [pfx+'A'+str(i) for i in range(6)]
155        RecpCellTerms[pId] = G2lat.cell2A(Phases[phase]['General']['Cell'][1:7])
156        zeroDict[pId] = dict(zip(RcellLbls[pId],6*[0.,]))
157        SGdata[pId] = Phases[phase]['General']['SGData']
158        laue = SGdata[pId]['SGLaue']
159        if laue == '2/m':
160            laue += SGdata[pId]['SGUniq']
161        for symlist,celllist in G2lat.UniqueCellByLaue:
162            if laue in symlist:
163                uniqCellIndx[pId] = celllist
164                break
165        else: # should not happen
166            uniqCellIndx[pId] = list(range(6))
167           
168    # scan for locations where the variables change
169    VaryListChanges = [] # histograms where there is a change
170    combinedVaryList = []
171    firstValueDict = {}
172    vallookup = {}
173    posdict = {}
174    prevVaryList = []
175    foundNames = []
176    missing = 0
177    for i,name in enumerate(seqHistList):
178        if name not in seqData:
179            if missing < 5:
180                print(" Warning: "+name+" not found")
181            elif missing == 5:
182                print (' Warning: more are missing')
183            missing += 1
184            continue
185        foundNames.append(name)
186        maxPWL = 5
187        for var,val,sig in zip(seqData[name]['varyList'],seqData[name]['variables'],seqData[name]['sig']):
188            svar = striphist(var,'*') # wild-carded
189            if 'PWL' in svar:
190                if int(svar.split(':')[-1]) > maxPWL:
191                    continue
192            if svar not in combinedVaryList:
193                # add variables to list as they appear
194                combinedVaryList.append(svar)
195                firstValueDict[svar] = (val,sig)
196        if prevVaryList != seqData[name]['varyList']: # this refinement has a different refinement list from previous
197            prevVaryList = seqData[name]['varyList']
198            vallookup[name] = dict(zip(seqData[name]['varyList'],seqData[name]['variables']))
199            posdict[name] = {}
200            for var in seqData[name]['varyList']:
201                svar = striphist(var,'*')
202                if 'PWL' in svar:
203                    if int(svar.split(':')[-1]) > maxPWL:
204                        continue
205                posdict[name][combinedVaryList.index(svar)] = svar
206            VaryListChanges.append(name)
207    if missing:
208        print (' Warning: Total of %d data sets missing from sequential results'%(missing))
209
210    #### --- start building table
211    histNames = foundNames
212#            sampleParms = GetSampleParms()
213    nRows = len(histNames)
214    tblValues = [list(range(nRows))]   # table of values arranged by columns
215    tblSigs =   [None]                 # a list of sigma values, or None if not defined
216    tblLabels = ['Number']             # a label for the column
217    tblTypes = ['int']
218    # start with Rwp values
219    tblValues += [[seqData[name]['Rvals']['Rwp'] for name in histNames]]
220    tblSigs += [None]
221    tblLabels += ['Rwp']
222    tblTypes += ['10,3']
223    # add changing sample parameters to table
224    sampleParmDict = {'Temperature':[],'Pressure':[],'Time':[],
225                 'FreePrm1':[],'FreePrm2':[],'FreePrm3':[],'Omega':[],
226                 'Chi':[],'Phi':[],'Azimuth':[],}
227    for key in sampleParmDict:
228        for h in histNames:
229            var = ":" + str(Histograms[h]['hId']) + ":" + key
230            sampleParmDict[key].append(seqData[h]['parmDict'].get(var))
231        if not np.all(np.array(sampleParmDict[key]) == sampleParmDict[key][0]):
232            tblValues += [sampleParmDict[key]]
233            tblSigs.append(None)
234            if 'FreePrm' in key and key in Controls:
235                tblLabels.append(Controls[item])
236            else:
237                tblLabels.append(key)
238            tblTypes += ['float']
239
240    # add unique cell parameters 
241    if len(newCellDict):
242        for pId in sorted(RecpCellTerms):
243            pfx = str(pId)+'::' # prefix for A values from phase
244            cells = []
245            cellESDs = []
246            Albls = [pfx+'A'+str(i) for i in range(6)]
247            for name in histNames:
248                #if name not in Histograms: continue
249                hId = Histograms[name]['hId']
250                phfx = '%d:%d:'%(pId,hId)
251                esdLookUp = {}
252                dLookup = {}
253                for item in seqData[name]['newCellDict']:
254                    if phfx+item.split('::')[1] in seqData[name]['varyList']:
255                        esdLookUp[newCellDict[item][0]] = item
256                        dLookup[item] = newCellDict[item][0]
257                covData = {'varyList': [dLookup.get(striphist(v),v) for v in seqData[name]['varyList']],
258                    'covMatrix': seqData[name]['covMatrix']}
259                A = RecpCellTerms[pId][:] # make copy of starting A values
260                # update with refined values
261                for i,j in enumerate(('D11','D22','D33','D12','D13','D23')):
262                    var = str(pId)+'::A'+str(i)
263                    Dvar = str(pId)+':'+str(hId)+':'+j
264                    # apply Dij value if non-zero
265                    if Dvar in seqData[name]['parmDict']:
266                        parmDict = seqData[name]['parmDict']
267                        if parmDict[Dvar] != 0.0:
268                            A[i] += parmDict[Dvar]
269                    # override with fit result if is Dij varied
270                    if var in cellAlist:
271                        try:
272                            A[i] = seqData[name]['newCellDict'][esdLookUp[var]][1] # get refined value
273                        except KeyError:
274                            pass
275                # apply symmetry
276                cellDict = dict(zip(Albls,A))
277                try:    # convert to direct cell
278                    A,zeros = G2stIO.cellFill(pfx,SGdata[pId],cellDict,zeroDict[pId])
279                    c = G2lat.A2cell(A)
280                    vol = G2lat.calc_V(A)
281                    cE = G2stIO.getCellEsd(pfx,SGdata[pId],A,covData)
282                except:
283                    c = 6*[None]
284                    cE = 6*[None]
285                    vol = None
286                # add only unique values to table
287                if name in Phases[phaseLookup[pId]]['Histograms']:
288                    cells += [[c[i] for i in uniqCellIndx[pId]]+[vol]]
289                    cellESDs += [[cE[i] for i in uniqCellIndx[pId]]+[cE[-1]]]
290                else:
291                    cells += [[None for i in uniqCellIndx[pId]]+[None]]
292                    cellESDs += [[None for i in uniqCellIndx[pId]]+[None]]
293            p = phaseLookup[pId]
294            tblLabels += ['{}, {}'.format(G2lat.cellAlbl[i],p) for i in uniqCellIndx[pId]]
295            tblTypes += ['10,5' if i <3 else '10,3' for i in uniqCellIndx[pId]]
296            tblLabels.append('{}, {}'.format('Volume',p))
297            tblTypes += ['10,3']
298            tblValues += zip(*cells)
299            tblSigs += zip(*cellESDs)
300
301    # sort out the variables in their selected order
302    varcols = 0
303    varlbls = []
304    for d in posdict.values():
305        varcols = max(varcols,max(d.keys())+1)
306    # get labels for each column
307    for i in range(varcols):
308        lbl = ''
309        for h in VaryListChanges:
310            if posdict[h].get(i):
311                if posdict[h].get(i) in lbl: continue
312                if lbl != "": lbl += '/'
313                lbl += posdict[h].get(i)
314        varlbls.append(lbl)
315    vals = []
316    esds = []
317    varsellist = None        # will be a list of variable names in the order they are selected to appear
318    # tabulate values for each hist, leaving None for blank columns
319    for name in histNames:
320        if name in posdict:
321            varsellist = [posdict[name].get(i) for i in range(varcols)]
322            # translate variable names to how they will be used in the headings
323            vs = [striphist(v,'*') for v in seqData[name]['varyList']]
324            # determine the index for each column (or None) in the seqData[]['variables'] and ['sig'] lists
325            sellist = [vs.index(v) if v is not None else None for v in varsellist]
326            #sellist = [i if striphist(v,'*') in varsellist else None for i,v in enumerate(seqData[name]['varyList'])]
327        if not varsellist: raise Exception()
328        vals.append([seqData[name]['variables'][s] if s is not None else None for s in sellist])
329        esds.append([seqData[name]['sig'][s] if s is not None else None for s in sellist])
330    tblValues += zip(*vals)
331    tblSigs += zip(*esds)
332    tblLabels += varlbls
333    tblTypes += ['float' for i in varlbls]
334   
335    # tabulate constrained variables, removing histogram numbers if needed
336    # from parameter label
337    depValDict = {}
338    depSigDict = {}
339    for name in histNames:
340        for var in seqData[name].get('depParmDict',{}):
341            val,sig = seqData[name]['depParmDict'][var]
342            svar = striphist(var,'*')
343            if svar not in depValDict:
344               depValDict[svar] = [val]
345               depSigDict[svar] = [sig]
346            else:
347               depValDict[svar].append(val)
348               depSigDict[svar].append(sig)
349
350    # add the dependent constrained variables to the table
351    for var in sorted(depValDict):
352        if len(depValDict[var]) != len(histNames): continue
353        tblLabels.append(var)
354        tblTypes.append('10,5')
355        tblSigs += [depSigDict[var]]
356        tblValues += [depValDict[var]]
357
358    # add refined atom parameters to table
359    for parm in sorted(atomLookup):
360        tblLabels.append(parm)
361        tblTypes.append('10,5')
362        tblValues += [[seqData[name]['newAtomDict'][atomLookup[parm]][1] for name in histNames]]
363        if atomLookup[parm] in seqData[histNames[0]]['varyList']:
364            col = seqData[histNames[0]]['varyList'].index(atomLookup[parm])
365            tblSigs += [[seqData[name]['sig'][col] for name in histNames]]
366        else:
367            tblSigs += [None]
368
369    # compute and add weight fractions to table if varied
370    for phase in Phases:
371        var = str(Phases[phase]['pId'])+':*:Scale'
372        if var not in combinedVaryList+list(depValDict.keys()): continue
373        wtFrList = []
374        sigwtFrList = []
375        for i,name in enumerate(histNames):
376            if name not in Phases[phase]['Histograms']:
377                wtFrList.append(None)
378                sigwtFrList.append(0.0)
379                continue
380            elif not Phases[phase]['Histograms'][name]['Use']:
381                wtFrList.append(None)
382                sigwtFrList.append(0.0)
383                continue
384            wtFrSum = 0.
385            for phase1 in Phases:
386                if name not in Phases[phase1]['Histograms']: continue
387                if not Phases[phase1]['Histograms'][name]['Use']: continue
388                wtFrSum += Phases[phase1]['Histograms'][name]['Scale'][0]*Phases[phase1]['General']['Mass']
389            var = str(Phases[phase]['pId'])+':'+str(i)+':Scale'
390            wtFr = Phases[phase]['Histograms'][name]['Scale'][0]*Phases[phase]['General']['Mass']/wtFrSum
391            wtFrList.append(wtFr)
392            if var in seqData[name]['varyList']:
393                sig = seqData[name]['sig'][seqData[name]['varyList'].index(var)]*wtFr/Phases[phase]['Histograms'][name]['Scale'][0]
394            elif var in seqData[name].get('depParmDict',{}):
395                _,sig = seqData[name]['depParmDict'][var]
396            else:
397                sig = 0.0
398            sigwtFrList.append(sig)
399        p = phaseLookup[Phases[phase]['pId']]
400        tblLabels.append(p + ' Wgt Frac')
401        tblTypes.append('10,4')
402        tblValues += [wtFrList]
403        tblSigs += [sigwtFrList]
404    return tblLabels,tblValues,tblSigs,tblTypes
405
406
407# Refactored over here to allow access by GSASIIscriptable.py
408def WriteCIFitem(fp, name, value=''):
409    '''Helper function for writing CIF output. Translated from exports/G2export_CIF.py'''
410    # Ignore unicode issues
411    if value:
412        if "\n" in value or len(value)> 70:
413            if name.strip():
414                fp.write(name+'\n')
415            fp.write(';\n'+value+'\n')
416            fp.write(';'+'\n')
417        elif " " in value:
418            if len(name)+len(value) > 65:
419                fp.write(name + '\n   ' + '"' + str(value) + '"'+'\n')
420            else:
421                fp.write(name + '  ' + '"' + str(value) + '"'+'\n')
422        else:
423            if len(name)+len(value) > 65:
424                fp.write(name+'\n   ' + value+'\n')
425            else:
426                fp.write(name+'  ' + value+'\n')
427    else:
428        fp.write(name+'\n')
429
430def RBheader(fp):
431    WriteCIFitem(fp,'\n# RIGID BODY DETAILS')
432    WriteCIFitem(fp,'loop_\n    _restr_rigid_body_class.class_id\n    _restr_rigid_body_class.details')
433
434# Refactored over here to allow access by GSASIIscriptable.py
435def WriteAtomsNuclear(fp, phasedict, phasenam, parmDict, sigDict, labellist,
436                          RBparms={}):
437    'Write atom positions to CIF'
438    # phasedict = self.Phases[phasenam] # pointer to current phase info
439    General = phasedict['General']
440    cx,ct,cs,cia = General['AtomPtrs']
441    GS = G2lat.cell2GS(General['Cell'][1:7])
442    Amat = G2lat.cell2AB(General['Cell'][1:7])[0]
443    Atoms = phasedict['Atoms']
444    cfrac = cx+3
445    fpfx = str(phasedict['pId'])+'::Afrac:'
446    for i,at in enumerate(Atoms):
447        fval = parmDict.get(fpfx+str(i),at[cfrac])
448        if fval != 0.0:
449            break
450    else:
451        WriteCIFitem(fp, '\n# PHASE HAS NO ATOMS!')
452        return
453
454    WriteCIFitem(fp, '\n# ATOMIC COORDINATES AND DISPLACEMENT PARAMETERS')
455    WriteCIFitem(fp, 'loop_ '+
456                 '\n   _atom_site_label'+
457                 '\n   _atom_site_type_symbol'+
458                 '\n   _atom_site_fract_x'+
459                 '\n   _atom_site_fract_y'+
460                 '\n   _atom_site_fract_z'+
461                 '\n   _atom_site_occupancy'+
462                 '\n   _atom_site_adp_type'+
463                 '\n   _atom_site_U_iso_or_equiv'+
464                 '\n   _atom_site_site_symmetry_multiplicity')
465
466    varnames = {cx:'Ax',cx+1:'Ay',cx+2:'Az',cx+3:'Afrac',
467                cia+1:'AUiso',cia+2:'AU11',cia+3:'AU22',cia+4:'AU33',
468                cia+5:'AU12',cia+6:'AU13',cia+7:'AU23'}
469    # Empty the labellist
470    while labellist:
471        labellist.pop()
472
473    pfx = str(phasedict['pId'])+'::'
474    # loop over all atoms
475    naniso = 0
476    for i,at in enumerate(Atoms):
477        if phasedict['General']['Type'] == 'macromolecular':
478            label = '%s_%s_%s_%s'%(at[ct-1],at[ct-3],at[ct-4],at[ct-2])
479            s = PutInCol(MakeUniqueLabel(label,labellist),15) # label
480        else:
481            s = PutInCol(MakeUniqueLabel(at[ct-1],labellist),6) # label
482        fval = parmDict.get(fpfx+str(i),at[cfrac])
483        if fval == 0.0: continue # ignore any atoms that have a occupancy set to 0 (exact)
484        s += PutInCol(FmtAtomType(at[ct]),4) # type
485        if at[cia] == 'I':
486            adp = 'Uiso '
487        else:
488            adp = 'Uani '
489            naniso += 1
490            t = G2lat.Uij2Ueqv(at[cia+2:cia+8],GS,Amat)[0]
491            for j in (2,3,4):
492                var = pfx+varnames[cia+j]+":"+str(i)
493        for j in (cx,cx+1,cx+2,cx+3,cia,cia+1):
494            if j in (cx,cx+1,cx+2):
495                dig = 11
496                sigdig = -0.00009
497            else:
498                dig = 10
499                sigdig = -0.0009
500            if j == cia:
501                s += adp
502            else:
503                var = pfx+varnames[j]+":"+str(i)
504                dvar = pfx+"d"+varnames[j]+":"+str(i)
505                if dvar not in sigDict:
506                    dvar = var
507                if j == cia+1 and adp == 'Uani ':
508                    sig = sigdig
509                    val = t
510                else:
511                    #print var,(var in parmDict),(var in sigDict)
512                    val = parmDict.get(var,at[j])
513                    sig = sigDict.get(dvar,sigdig)
514                    if dvar in G2mv.GetDependentVars(): # do not include an esd for dependent vars
515                        sig = -abs(sig)
516                s += PutInCol(G2mth.ValEsd(val,sig),dig)
517        s += PutInCol(at[cs+1],3)
518        WriteCIFitem(fp, s)
519    if naniso != 0: 
520        # now loop over aniso atoms
521        WriteCIFitem(fp, '\nloop_' + '\n   _atom_site_aniso_label' +
522                     '\n   _atom_site_aniso_U_11' + '\n   _atom_site_aniso_U_22' +
523                     '\n   _atom_site_aniso_U_33' + '\n   _atom_site_aniso_U_12' +
524                     '\n   _atom_site_aniso_U_13' + '\n   _atom_site_aniso_U_23')
525        for i,at in enumerate(Atoms):
526            fval = parmDict.get(fpfx+str(i),at[cfrac])
527            if fval == 0.0: continue # ignore any atoms that have a occupancy set to 0 (exact)
528            if at[cia] == 'I': continue
529            s = PutInCol(labellist[i],6) # label
530            for j in (2,3,4,5,6,7):
531                sigdig = -0.0009
532                var = pfx+varnames[cia+j]+":"+str(i)
533                val = parmDict.get(var,at[cia+j])
534                sig = sigDict.get(var,sigdig)
535                s += PutInCol(G2mth.ValEsd(val,sig),11)
536            WriteCIFitem(fp, s)
537    # save information about rigid bodies
538    header = False
539    num = 0
540    rbAtoms = []
541    for irb,RBObj in enumerate(phasedict['RBModels'].get('Residue',[])):
542        if not header:
543            header = True
544            RBheader(fp)
545        jrb = RBparms['RBIds']['Residue'].index(RBObj['RBId'])
546        rbsx = str(irb)+':'+str(jrb)
547        num += 1
548        WriteCIFitem(fp,'',str(num))
549        RBModel = RBparms['Residue'][RBObj['RBId']]
550        SGData = phasedict['General']['SGData']
551        Sytsym,Mult = G2spc.SytSym(RBObj['Orig'][0],SGData)[:2]
552        s = '''GSAS-II residue rigid body "{}" with {} atoms
553  Site symmetry @ origin: {}, multiplicity: {}
554'''.format(RBObj['RBname'],len(RBModel['rbTypes']),Sytsym,Mult)
555        for i in G2stIO.WriteResRBModel(RBModel):
556            s += i
557        s += '\n Location:\n'
558        for i in G2stIO.WriteRBObjPOAndSig(pfx,'RBR',rbsx,parmDict,sigDict):
559            s += i+'\n'
560        for i in G2stIO.WriteRBObjTLSAndSig(pfx,'RBR',rbsx,
561                        RBObj['ThermalMotion'][0],parmDict,sigDict):
562            s += i
563        nTors = len(RBObj['Torsions'])
564        if nTors:
565            for i in G2stIO.WriteRBObjTorAndSig(pfx,rbsx,parmDict,sigDict,
566                        nTors):
567                s += i
568        WriteCIFitem(fp,'',s.rstrip())
569       
570        pId = phasedict['pId']
571        for i in RBObj['Ids']:
572            lbl = G2obj.LookupAtomLabel(pId,G2obj.LookupAtomId(pId,i))[0]
573            rbAtoms.append('{:7s} 1_555 {:3d} ?'.format(lbl,num))
574        #GSASIIpath.IPyBreak()
575
576    for irb,RBObj in enumerate(phasedict['RBModels'].get('Vector',[])):
577        if not header:
578            header = True
579            RBheader(fp)
580        jrb = RBparms['RBIds']['Vector'].index(RBObj['RBId'])
581        rbsx = str(irb)+':'+str(jrb)
582        num += 1
583        WriteCIFitem(fp,'',str(num))
584        RBModel = RBparms['Vector'][RBObj['RBId']]
585        SGData = phasedict['General']['SGData']
586        Sytsym,Mult = G2spc.SytSym(RBObj['Orig'][0],SGData)[:2]
587        s = '''GSAS-II vector rigid body "{}" with {} atoms
588  Site symmetry @ origin: {}, multiplicity: {}
589'''.format(RBObj['RBname'],len(RBModel['rbTypes']),Sytsym,Mult)
590        for i in G2stIO.WriteVecRBModel(RBModel,sigDict,irb):
591            s += i
592        s += '\n Location:\n'
593        for i in G2stIO.WriteRBObjPOAndSig(pfx,'RBV',rbsx,parmDict,sigDict):
594            s += i+'\n'
595        for i in G2stIO.WriteRBObjTLSAndSig(pfx,'RBV',rbsx,
596                        RBObj['ThermalMotion'][0],parmDict,sigDict):
597            s += i
598        WriteCIFitem(fp,'',s.rstrip())
599       
600        pId = phasedict['pId']
601        for i in RBObj['Ids']:
602            lbl = G2obj.LookupAtomLabel(pId,G2obj.LookupAtomId(pId,i))[0]
603            rbAtoms.append('{:7s} 1_555 {:3d} ?'.format(lbl,num))
604
605    if rbAtoms:
606        WriteCIFitem(fp,'loop_\n    _restr_rigid_body.id'+
607            '\n    _restr_rigid_body.atom_site_label\n    _restr_rigid_body.site_symmetry'+
608            '\n    _restr_rigid_body.class_id\n    _restr_rigid_body.details')
609        for i,l in enumerate(rbAtoms):
610            WriteCIFitem(fp,'   {:5d} {}'.format(i+1,l))
611           
612def WriteAtomsMagnetic(fp, phasedict, phasenam, parmDict, sigDict, labellist):
613    'Write atom positions to CIF'
614    # phasedict = self.Phases[phasenam] # pointer to current phase info
615    General = phasedict['General']
616    cx,ct,cs,cia = General['AtomPtrs']
617    Atoms = phasedict['Atoms']
618    cfrac = cx+3
619    fpfx = str(phasedict['pId'])+'::Afrac:'
620    for i,at in enumerate(Atoms):
621        fval = parmDict.get(fpfx+str(i),at[cfrac])
622        if fval != 0.0:
623            break
624    else:
625        WriteCIFitem(fp, '\n# PHASE HAS NO ATOMS!')
626        return
627
628    WriteCIFitem(fp, '\n# ATOMIC COORDINATES AND DISPLACEMENT PARAMETERS')
629    WriteCIFitem(fp, 'loop_ '+
630                 '\n   _atom_site_label'+
631                 '\n   _atom_site_type_symbol'+
632                 '\n   _atom_site_fract_x'+
633                 '\n   _atom_site_fract_y'+
634                 '\n   _atom_site_fract_z'+
635                 '\n   _atom_site_occupancy'+
636                 '\n   _atom_site_adp_type'+
637                 '\n   _atom_site_U_iso_or_equiv'+
638                 '\n   _atom_site_site_symmetry_multiplicity')
639
640    varnames = {cx:'Ax',cx+1:'Ay',cx+2:'Az',cx+3:'Afrac',
641                cx+4:'AMx',cx+5:'AMy',cx+6:'AMz',
642                cia+1:'AUiso',cia+2:'AU11',cia+3:'AU22',cia+4:'AU33',
643                cia+5:'AU12',cia+6:'AU13',cia+7:'AU23'}
644    # Empty the labellist
645    while labellist:
646        labellist.pop()
647
648    pfx = str(phasedict['pId'])+'::'
649    # loop over all atoms
650    naniso = 0
651    for i,at in enumerate(Atoms):
652        if phasedict['General']['Type'] == 'macromolecular':
653            label = '%s_%s_%s_%s'%(at[ct-1],at[ct-3],at[ct-4],at[ct-2])
654            s = PutInCol(MakeUniqueLabel(label,labellist),15) # label
655        else:
656            s = PutInCol(MakeUniqueLabel(at[ct-1],labellist),6) # label
657        fval = parmDict.get(fpfx+str(i),at[cfrac])
658        if fval == 0.0: continue # ignore any atoms that have a occupancy set to 0 (exact)
659        s += PutInCol(FmtAtomType(at[ct]),4) # type
660        if at[cia] == 'I':
661            adp = 'Uiso '
662        else:
663            adp = 'Uani '
664            naniso += 1
665            # compute Uequiv crudely
666            # correct: Defined as "1/3 trace of diagonalized U matrix".
667            # SEE cell2GS & Uij2Ueqv to GSASIIlattice. Former is needed to make the GS matrix used by the latter.
668            t = 0.0
669            for j in (2,3,4):
670                var = pfx+varnames[cia+j]+":"+str(i)
671                t += parmDict.get(var,at[cia+j])
672        for j in (cx,cx+1,cx+2,cx+3,cia,cia+1):
673            if j in (cx,cx+1,cx+2):
674                dig = 11
675                sigdig = -0.00009
676            else:
677                dig = 10
678                sigdig = -0.009
679            if j == cia:
680                s += adp
681            else:
682                var = pfx+varnames[j]+":"+str(i)
683                dvar = pfx+"d"+varnames[j]+":"+str(i)
684                if dvar not in sigDict:
685                    dvar = var
686                if j == cia+1 and adp == 'Uani ':
687                    val = t/3.
688                    sig = sigdig
689                else:
690                    #print var,(var in parmDict),(var in sigDict)
691                    val = parmDict.get(var,at[j])
692                    sig = sigDict.get(dvar,sigdig)
693                    if dvar in G2mv.GetDependentVars(): # do not include an esd for dependent vars
694                        sig = -abs(sig)
695                s += PutInCol(G2mth.ValEsd(val,sig),dig)
696        s += PutInCol(at[cs+1],3)
697        WriteCIFitem(fp, s)
698    if naniso: 
699        # now loop over aniso atoms
700        WriteCIFitem(fp, '\nloop_' + '\n   _atom_site_aniso_label' +
701                     '\n   _atom_site_aniso_U_11' + '\n   _atom_site_aniso_U_22' +
702                     '\n   _atom_site_aniso_U_33' + '\n   _atom_site_aniso_U_12' +
703                     '\n   _atom_site_aniso_U_13' + '\n   _atom_site_aniso_U_23')
704        for i,at in enumerate(Atoms):
705            fval = parmDict.get(fpfx+str(i),at[cfrac])
706            if fval == 0.0: continue # ignore any atoms that have a occupancy set to 0 (exact)
707            if at[cia] == 'I': continue
708            s = PutInCol(labellist[i],6) # label
709            for j in (2,3,4,5,6,7):
710                sigdig = -0.0009
711                var = pfx+varnames[cia+j]+":"+str(i)
712                val = parmDict.get(var,at[cia+j])
713                sig = sigDict.get(var,sigdig)
714                s += PutInCol(G2mth.ValEsd(val,sig),11)
715            WriteCIFitem(fp, s)
716    # now loop over mag atoms (e.g. all of them)
717    WriteCIFitem(fp, '\nloop_' + '\n   _atom_site_moment.label' +
718                 '\n   _atom_site_moment.crystalaxis_x' +
719                 '\n   _atom_site_moment.crystalaxis_y' +
720                 '\n   _atom_site_moment.crystalaxis_z')
721    for i,at in enumerate(Atoms):
722        fval = parmDict.get(fpfx+str(i),at[cfrac])
723        if fval == 0.0: continue # ignore any atoms that have a occupancy set to 0 (exact)
724        s = PutInCol(labellist[i],6) # label
725        for j in (cx+4,cx+5,cx+6):
726            sigdig = -0.0009
727            var = pfx+varnames[j]+":"+str(i)
728            val = parmDict.get(var,at[j])
729            sig = sigDict.get(var,sigdig)
730            s += PutInCol(G2mth.ValEsd(val,sig),11)
731        WriteCIFitem(fp, s)
732
733def WriteAtomsMM(fp, phasedict, phasenam, parmDict, sigDict,
734                          RBparms={}):
735    'Write atom positions to CIF using mmCIF items'
736    # phasedict = self.Phases[phasenam] # pointer to current phase info
737    General = phasedict['General']
738    cx,ct,cs,cia = General['AtomPtrs']
739    GS = G2lat.cell2GS(General['Cell'][1:7])
740    Amat = G2lat.cell2AB(General['Cell'][1:7])[0]
741    Atoms = phasedict['Atoms']
742    cfrac = cx+3
743    fpfx = str(phasedict['pId'])+'::Afrac:'
744    for i,at in enumerate(Atoms):
745        fval = parmDict.get(fpfx+str(i),at[cfrac])
746        if fval != 0.0:
747            break
748    else:
749        WriteCIFitem(fp, '\n# PHASE HAS NO ATOMS!')
750        return
751
752    WriteCIFitem(fp, '\n# ATOMIC COORDINATES AND DISPLACEMENT PARAMETERS')
753    WriteCIFitem(fp, 'loop_ '+
754                 '\n   _atom_site.id'+
755                 '\n   _atom_site.type_symbol'+
756                 '\n   _atom_site.label_atom_id'+
757                 '\n   _atom_site.label_alt_id'+
758                 '\n   _atom_site.label_comp_id'+
759                 '\n   _atom_site.label_asym_id'+
760                 '\n   _atom_site.label_entity_id'+
761                 '\n   _atom_site.label_seq_id'+
762                 '\n   _atom_site.pdbx_PDB_ins_code'+
763                 '\n   _atom_site.fract_x'+
764                 '\n   _atom_site.fract_y'+
765                 '\n   _atom_site.fract_z'+
766                 '\n   _atom_site.occupancy'+
767#                 '\n   _atom_site_adp_type'+
768                 '\n   _atom_site.U_iso_or_equiv'
769#                 '\n   _atom_site_site_symmetry_multiplicity'
770                 )
771# _atom_site.group_PDB
772# _atom_site.Cartn_x
773# _atom_site.Cartn_y
774# _atom_site.Cartn_z
775# _atom_site.Cartn_x_esd
776# _atom_site.Cartn_y_esd
777# _atom_site.Cartn_z_esd
778# _atom_site.occupancy_esd
779# _atom_site.B_iso_or_equiv_esd
780# _atom_site.pdbx_formal_charge
781# _atom_site.auth_seq_id
782# _atom_site.auth_comp_id
783# _atom_site.auth_asym_id
784# _atom_site.auth_atom_id
785# _atom_site.pdbx_PDB_model_num
786    varnames = {cx:'Ax',cx+1:'Ay',cx+2:'Az',cx+3:'Afrac',
787                cia+1:'AUiso',cia+2:'AU11',cia+3:'AU22',cia+4:'AU33',
788                cia+5:'AU12',cia+6:'AU13',cia+7:'AU23'}
789
790    pfx = str(phasedict['pId'])+'::'
791    # loop over all atoms
792    naniso = 0
793    for i,at in enumerate(Atoms):
794        s = ''
795        s += PutInCol(str(i),5) # atom number
796        s += PutInCol(FmtAtomType(at[ct]),4) # type
797        s += PutInCol(at[ct-1],4) # label_atom_id
798        s += PutInCol('.',2) # alt_id
799        s += PutInCol(at[ct-3],4) # comp_id
800        s += PutInCol(at[ct-2],3) # _asym_id
801        s += PutInCol(at[ct-4],3) # entity_id
802        s += PutInCol('?',2) # _seq_id
803        s += PutInCol('?',2) # pdbx_PDB_ins_code
804        fval = parmDict.get(fpfx+str(i),at[cfrac])
805        if fval == 0.0: continue # ignore any atoms that have a occupancy set to 0 (exact)
806        if at[cia] == 'I':
807            adp = 'Uiso '
808        else:
809            adp = 'Uani '
810            naniso += 1
811            t = G2lat.Uij2Ueqv(at[cia+2:cia+8],GS,Amat)[0]
812            for j in (2,3,4):
813                var = pfx+varnames[cia+j]+":"+str(i)
814        for j in (cx,cx+1,cx+2,cx+3):
815            if j in (cx,cx+1,cx+2):
816                dig = 11
817                sigdig = -0.00009
818            else:
819                dig = 5
820                sigdig = -0.09
821            var = pfx+varnames[j]+":"+str(i)
822            dvar = pfx+"d"+varnames[j]+":"+str(i)
823            if dvar not in sigDict:
824                dvar = var
825            #print var,(var in parmDict),(var in sigDict)
826            val = parmDict.get(var,at[j])
827            sig = sigDict.get(dvar,sigdig)
828            if dvar in G2mv.GetDependentVars(): # do not include an esd for dependent vars
829                sig = -abs(sig)
830            s += PutInCol(G2mth.ValEsd(val,sig),dig)
831        s += PutInCol(at[cs+1],3)
832        WriteCIFitem(fp, s)
833    # save information about rigid bodies
834#     header = False
835#     num = 0
836#     rbAtoms = []
837#     for irb,RBObj in enumerate(phasedict['RBModels'].get('Residue',[])):
838#         if not header:
839#             header = True
840#             RBheader(fp)
841#         jrb = RBparms['RBIds']['Residue'].index(RBObj['RBId'])
842#         rbsx = str(irb)+':'+str(jrb)
843#         num += 1
844#         WriteCIFitem(fp,'',str(num))
845#         RBModel = RBparms['Residue'][RBObj['RBId']]
846#         SGData = phasedict['General']['SGData']
847#         Sytsym,Mult = G2spc.SytSym(RBObj['Orig'][0],SGData)[:2]
848#         s = '''GSAS-II residue rigid body "{}" with {} atoms
849#   Site symmetry @ origin: {}, multiplicity: {}
850# '''.format(RBObj['RBname'],len(RBModel['rbTypes']),Sytsym,Mult)
851#         for i in G2stIO.WriteResRBModel(RBModel):
852#             s += i
853#         s += '\n Location:\n'
854#         for i in G2stIO.WriteRBObjPOAndSig(pfx,'RBR',rbsx,parmDict,sigDict):
855#             s += i+'\n'
856#         for i in G2stIO.WriteRBObjTLSAndSig(pfx,'RBR',rbsx,
857#                         RBObj['ThermalMotion'][0],parmDict,sigDict):
858#             s += i
859#         nTors = len(RBObj['Torsions'])
860#         if nTors:
861#             for i in G2stIO.WriteRBObjTorAndSig(pfx,rbsx,parmDict,sigDict,
862#                         nTors):
863#                 s += i
864#         WriteCIFitem(fp,'',s.rstrip())
865       
866#         pId = phasedict['pId']
867#         for i in RBObj['Ids']:
868#             lbl = G2obj.LookupAtomLabel(pId,G2obj.LookupAtomId(pId,i))[0]
869#             rbAtoms.append('{:7s} 1_555 {:3d} ?'.format(lbl,num))
870#         #GSASIIpath.IPyBreak()
871
872#     for irb,RBObj in enumerate(phasedict['RBModels'].get('Vector',[])):
873#         if not header:
874#             header = True
875#             RBheader(fp)
876#         jrb = RBparms['RBIds']['Vector'].index(RBObj['RBId'])
877#         rbsx = str(irb)+':'+str(jrb)
878#         num += 1
879#         WriteCIFitem(fp,'',str(num))
880#         RBModel = RBparms['Vector'][RBObj['RBId']]
881#         SGData = phasedict['General']['SGData']
882#         Sytsym,Mult = G2spc.SytSym(RBObj['Orig'][0],SGData)[:2]
883#         s = '''GSAS-II vector rigid body "{}" with {} atoms
884#   Site symmetry @ origin: {}, multiplicity: {}
885# '''.format(RBObj['RBname'],len(RBModel['rbTypes']),Sytsym,Mult)
886#         for i in G2stIO.WriteVecRBModel(RBModel,sigDict,irb):
887#             s += i
888#         s += '\n Location:\n'
889#         for i in G2stIO.WriteRBObjPOAndSig(pfx,'RBV',rbsx,parmDict,sigDict):
890#             s += i+'\n'
891#         for i in G2stIO.WriteRBObjTLSAndSig(pfx,'RBV',rbsx,
892#                         RBObj['ThermalMotion'][0],parmDict,sigDict):
893#             s += i
894#         WriteCIFitem(fp,'',s.rstrip())
895       
896#         pId = phasedict['pId']
897#         for i in RBObj['Ids']:
898#             lbl = G2obj.LookupAtomLabel(pId,G2obj.LookupAtomId(pId,i))[0]
899#             rbAtoms.append('{:7s} 1_555 {:3d} ?'.format(lbl,num))
900
901#     if rbAtoms:
902#         WriteCIFitem(fp,'loop_\n    _restr_rigid_body.id'+
903#             '\n    _restr_rigid_body.atom_site_label\n    _restr_rigid_body.site_symmetry'+
904#             '\n    _restr_rigid_body.class_id\n    _restr_rigid_body.details')
905#         for i,l in enumerate(rbAtoms):
906#             WriteCIFitem(fp,'   {:5d} {}'.format(i+1,l))
907
908# Refactored over here to allow access by GSASIIscriptable.py
909def WriteSeqAtomsNuclear(fp, cell, phasedict, phasenam, hist, seqData, RBparms):
910    'Write atom positions to CIF'
911    General = phasedict['General']
912    cx,ct,cs,cia = General['AtomPtrs']
913    GS = G2lat.cell2GS(cell[:6])
914    Amat = G2lat.cell2AB(cell[:6])[0]
915
916    # phasedict = self.Phases[phasenam] # pointer to current phase info
917    parmDict = seqData[hist]['parmDict']
918    sigDict = dict(zip(seqData[hist]['varyList'],seqData[hist]['sig']))
919    Atoms = phasedict['Atoms']
920    cfrac = cx+3
921    fpfx = str(phasedict['pId'])+'::Afrac:'
922    for i,at in enumerate(Atoms):
923        fval = parmDict.get(fpfx+str(i),at[cfrac])
924        if fval != 0.0:
925            break
926    else:
927        WriteCIFitem(fp, '\n# PHASE HAS NO ATOMS!')
928        return
929
930    WriteCIFitem(fp, '\n# ATOMIC COORDINATES AND DISPLACEMENT PARAMETERS')
931    WriteCIFitem(fp, 'loop_ '+
932                 '\n   _atom_site_label'+
933                 '\n   _atom_site_type_symbol'+
934                 '\n   _atom_site_fract_x'+
935                 '\n   _atom_site_fract_y'+
936                 '\n   _atom_site_fract_z'+
937                 '\n   _atom_site_occupancy'+
938                 '\n   _atom_site_adp_type'+
939                 '\n   _atom_site_U_iso_or_equiv'+
940                 '\n   _atom_site_site_symmetry_multiplicity')
941
942    varnames = {cx:'Ax',cx+1:'Ay',cx+2:'Az',cx+3:'Afrac',
943                cia+1:'AUiso',cia+2:'AU11',cia+3:'AU22',cia+4:'AU33',
944                cia+5:'AU12',cia+6:'AU13',cia+7:'AU23'}
945
946    labellist = []  # used to make atom labels unique as required in CIF
947    pfx = str(phasedict['pId'])+'::'
948    # loop over all atoms
949    naniso = 0
950
951    for i,at in enumerate(Atoms):
952        if phasedict['General']['Type'] == 'macromolecular':
953            label = '%s_%s_%s_%s'%(at[ct-1],at[ct-3],at[ct-4],at[ct-2])
954            s = PutInCol(MakeUniqueLabel(label,labellist),15) # label
955        else:
956            s = PutInCol(MakeUniqueLabel(at[ct-1],labellist),6) # label
957        fval = parmDict.get(fpfx+str(i),at[cfrac])
958        if fval == 0.0: continue # ignore any atoms that have a occupancy set to 0 (exact)
959        s += PutInCol(FmtAtomType(at[ct]),4) # type
960        if at[cia] == 'I':
961            adp = 'Uiso '
962        else:
963            adp = 'Uani '
964            naniso += 1
965            t = G2lat.Uij2Ueqv(at[cia+2:cia+8],GS,Amat)[0]
966            for j in (2,3,4):
967                var = pfx+varnames[cia+j]+":"+str(i)
968        for j in (cx,cx+1,cx+2,cx+3,cia,cia+1):
969            if j in (cx,cx+1,cx+2):
970                dig = 11
971                sigdig = -0.00009
972            else:
973                dig = 10
974                sigdig = -0.0009
975            if j == cia:
976                s += adp
977            else:
978                var = pfx+varnames[j]+":"+str(i)
979                dvar = pfx+"d"+varnames[j]+":"+str(i)
980                if dvar not in sigDict:
981                    dvar = var
982                if j == cia+1 and adp == 'Uani ':
983                    sig = sigdig
984                    val = t
985                else:
986                    #print var,(var in parmDict),(var in sigDict)
987                    val = parmDict.get(var,at[j])
988                    sig = sigDict.get(dvar,sigdig)
989                    if dvar in G2mv.GetDependentVars(): # do not include an esd for dependent vars
990                        sig = -abs(sig)
991                s += PutInCol(G2mth.ValEsd(val,sig),dig)
992        s += PutInCol(at[cs+1],3)
993        WriteCIFitem(fp, s)
994    if naniso != 0: 
995        # now loop over aniso atoms
996        WriteCIFitem(fp, '\nloop_' + '\n   _atom_site_aniso_label' +
997                     '\n   _atom_site_aniso_U_11' + '\n   _atom_site_aniso_U_22' +
998                     '\n   _atom_site_aniso_U_33' + '\n   _atom_site_aniso_U_12' +
999                     '\n   _atom_site_aniso_U_13' + '\n   _atom_site_aniso_U_23')
1000        for i,at in enumerate(Atoms):
1001            fval = parmDict.get(fpfx+str(i),at[cfrac])
1002            if fval == 0.0: continue # ignore any atoms that have a occupancy set to 0 (exact)
1003            if at[cia] == 'I': continue
1004            s = PutInCol(labellist[i],6) # label
1005            for j in (2,3,4,5,6,7):
1006                sigdig = -0.0009
1007                var = pfx+varnames[cia+j]+":"+str(i)
1008                val = parmDict.get(var,at[cia+j])
1009                sig = sigDict.get(var,sigdig)
1010                s += PutInCol(G2mth.ValEsd(val,sig),11)
1011            WriteCIFitem(fp, s)
1012    # save information about rigid bodies
1013    header = False
1014    num = 0
1015    rbAtoms = []
1016    for irb,RBObj in enumerate(phasedict['RBModels'].get('Residue',[])):
1017        if not header:
1018            header = True
1019            RBheader(fp)
1020        jrb = RBparms['RBIds']['Residue'].index(RBObj['RBId'])
1021        rbsx = str(irb)+':'+str(jrb)
1022        num += 1
1023        WriteCIFitem(fp,'',str(num))
1024        RBModel = RBparms['Residue'][RBObj['RBId']]
1025        SGData = phasedict['General']['SGData']
1026        Sytsym,Mult = G2spc.SytSym(RBObj['Orig'][0],SGData)[:2]
1027        s = '''GSAS-II residue rigid body "{}" with {} atoms
1028  Site symmetry @ origin: {}, multiplicity: {}
1029'''.format(RBObj['RBname'],len(RBModel['rbTypes']),Sytsym,Mult)
1030        for i in G2stIO.WriteResRBModel(RBModel):
1031            s += i
1032        s += '\n Location:\n'
1033        for i in G2stIO.WriteRBObjPOAndSig(pfx,'RBR',rbsx,parmDict,sigDict):
1034            s += i+'\n'
1035        for i in G2stIO.WriteRBObjTLSAndSig(pfx,'RBR',rbsx,
1036                        RBObj['ThermalMotion'][0],parmDict,sigDict):
1037            s += i
1038        nTors = len(RBObj['Torsions'])
1039        if nTors:
1040            for i in G2stIO.WriteRBObjTorAndSig(pfx,rbsx,parmDict,sigDict,
1041                        nTors):
1042                s += i
1043        WriteCIFitem(fp,'',s.rstrip())
1044       
1045        pId = phasedict['pId']
1046        for i in RBObj['Ids']:
1047            lbl = G2obj.LookupAtomLabel(pId,G2obj.LookupAtomId(pId,i))[0]
1048            rbAtoms.append('{:7s} 1_555 {:3d} ?'.format(lbl,num))
1049        #GSASIIpath.IPyBreak()
1050
1051    for irb,RBObj in enumerate(phasedict['RBModels'].get('Vector',[])):
1052        if not header:
1053            header = True
1054            RBheader(fp)
1055        jrb = RBparms['RBIds']['Vector'].index(RBObj['RBId'])
1056        rbsx = str(irb)+':'+str(jrb)
1057        num += 1
1058        WriteCIFitem(fp,'',str(num))
1059        RBModel = RBparms['Vector'][RBObj['RBId']]
1060        SGData = phasedict['General']['SGData']
1061        Sytsym,Mult = G2spc.SytSym(RBObj['Orig'][0],SGData)[:2]
1062        s = '''GSAS-II vector rigid body "{}" with {} atoms
1063  Site symmetry @ origin: {}, multiplicity: {}
1064'''.format(RBObj['RBname'],len(RBModel['rbTypes']),Sytsym,Mult)
1065        for i in G2stIO.WriteVecRBModel(RBModel,sigDict,irb):
1066            s += i
1067        s += '\n Location:\n'
1068        for i in G2stIO.WriteRBObjPOAndSig(pfx,'RBV',rbsx,parmDict,sigDict):
1069            s += i+'\n'
1070        for i in G2stIO.WriteRBObjTLSAndSig(pfx,'RBV',rbsx,
1071                        RBObj['ThermalMotion'][0],parmDict,sigDict):
1072            s += i
1073        WriteCIFitem(fp,'',s.rstrip())
1074       
1075        pId = phasedict['pId']
1076        for i in RBObj['Ids']:
1077            lbl = G2obj.LookupAtomLabel(pId,G2obj.LookupAtomId(pId,i))[0]
1078            rbAtoms.append('{:7s} 1_555 {:3d} ?'.format(lbl,num))
1079
1080    if rbAtoms:
1081        WriteCIFitem(fp,'loop_\n    _restr_rigid_body.id'+
1082            '\n    _restr_rigid_body.atom_site_label\n    _restr_rigid_body.site_symmetry'+
1083            '\n    _restr_rigid_body.class_id\n    _restr_rigid_body.details')
1084        for i,l in enumerate(rbAtoms):
1085            WriteCIFitem(fp,'   {:5d} {}'.format(i+1,l))
1086           
1087# Refactored over here to allow access by GSASIIscriptable.py
1088def MakeUniqueLabel(lbl, labellist):
1089    lbl = lbl.strip()
1090    if not lbl: # deal with a blank label
1091        lbl = 'A_1'
1092    if lbl not in labellist:
1093        labellist.append(lbl)
1094        return lbl
1095    i = 1
1096    prefix = lbl
1097    if '_' in lbl:
1098        prefix = lbl[:lbl.rfind('_')]
1099        suffix = lbl[lbl.rfind('_')+1:]
1100        try:
1101            i = int(suffix)+1
1102        except:
1103            pass
1104    while prefix+'_'+str(i) in labellist:
1105        i += 1
1106    else:
1107        lbl = prefix+'_'+str(i)
1108        labellist.append(lbl)
1109
1110
1111# Refactored over here to allow access by GSASIIscriptable.py
1112def HillSortElements(elmlist):
1113    '''Sort elements in "Hill" order: C, H, others, (where others
1114    are alphabetical).
1115
1116    :params list elmlist: a list of element strings
1117
1118    :returns: a sorted list of element strings
1119    '''
1120    newlist = []
1121    oldlist = elmlist[:]
1122    for elm in ('C','H'):
1123        if elm in elmlist:
1124            newlist.append(elm)
1125            oldlist.pop(oldlist.index(elm))
1126    return newlist+sorted(oldlist)
1127
1128
1129def FmtAtomType(sym):
1130    'Reformat a GSAS-II atom type symbol to match CIF rules'
1131    sym = sym.replace('_','') # underscores are not allowed: no isotope designation?
1132    # in CIF, oxidation state sign symbols come after, not before
1133    if '+' in sym:
1134        sym = sym.replace('+','') + '+'
1135    elif '-' in sym:
1136        sym = sym.replace('-','') + '-'
1137    return sym
1138
1139
1140def PutInCol(val, wid):
1141    val = str(val).replace(' ', '')
1142    if not val: val = '?'
1143    fmt = '{:' + str(wid) + '} '
1144    try:
1145        return fmt.format(val)
1146    except TypeError:
1147        return fmt.format('.')
1148
1149
1150# Refactored over here to allow access by GSASIIscriptable.py
1151def WriteComposition(fp, phasedict, phasenam, parmDict, quickmode=True, keV=None):
1152    '''determine the composition for the unit cell, crudely determine Z and
1153    then compute the composition in formula units.
1154
1155    If quickmode is False, then scattering factors are added to the element loop.
1156
1157    If keV is specified, then resonant scattering factors are also computed and included.
1158    '''
1159    General = phasedict['General']
1160    Z = General.get('cellZ',0.0)
1161    cx,ct,cs,cia = General['AtomPtrs']
1162    Atoms = phasedict['Atoms']
1163    fpfx = str(phasedict['pId'])+'::Afrac:'
1164    cfrac = cx+3
1165    cmult = cs+1
1166    compDict = {} # combines H,D & T
1167    sitemultlist = []
1168    massDict = dict(zip(General['AtomTypes'],General['AtomMass']))
1169    cellmass = 0
1170    elmLookup = {}
1171    for i,at in enumerate(Atoms):
1172        atype = at[ct].strip()
1173        if atype.find('-') != -1: atype = atype.split('-')[0]
1174        if atype.find('+') != -1: atype = atype.split('+')[0]
1175        atype = atype[0].upper()+atype[1:2].lower() # force case conversion
1176        if atype == "D" or atype == "D": atype = "H"
1177        fvar = fpfx+str(i)
1178        fval = parmDict.get(fvar,at[cfrac])
1179        mult = at[cmult]
1180        if not massDict.get(at[ct]):
1181            print('Error: No mass found for atom type '+at[ct])
1182            print('Will not compute cell contents for phase '+phasenam)
1183            return
1184        cellmass += massDict[at[ct]]*mult*fval
1185        compDict[atype] = compDict.get(atype,0.0) + mult*fval
1186        elmLookup[atype] = at[ct].strip()
1187        if fval == 1: sitemultlist.append(mult)
1188    if len(compDict.keys()) == 0: return # no elements!
1189    if Z < 1: # Z has not been computed or set by user
1190        Z = 1
1191        if not sitemultlist:
1192            General['cellZ'] = 1
1193            return
1194        for i in range(2,min(sitemultlist)+1):
1195            for m in sitemultlist:
1196                if m % i != 0:
1197                    break
1198                else:
1199                    Z = i
1200        General['cellZ'] = Z # save it
1201
1202    if not quickmode:
1203        FFtable = G2el.GetFFtable(General['AtomTypes'])
1204        BLtable = G2el.GetBLtable(General)
1205
1206    WriteCIFitem(fp, '\nloop_  _atom_type_symbol _atom_type_number_in_cell')
1207    s = '       '
1208    if not quickmode:
1209        for j in ('a1','a2','a3','a4','b1','b2','b3','b4','c',2,1):
1210            if len(s) > 80:
1211                WriteCIFitem(fp, s)
1212                s = '       '
1213            if j==1:
1214                s += ' _atom_type_scat_source'
1215            elif j==2:
1216                s += ' _atom_type_scat_length_neutron'
1217            else:
1218                s += ' _atom_type_scat_Cromer_Mann_'
1219                s += j
1220        if keV:
1221            WriteCIFitem(fp, s)
1222            s = '        _atom_type_scat_dispersion_real _atom_type_scat_dispersion_imag _atom_type_scat_dispersion_source'
1223        WriteCIFitem(fp, s)
1224
1225           
1226    formula = ''
1227    for elem in HillSortElements(list(compDict.keys())):
1228        s = '  '
1229        elmsym = elmLookup[elem]
1230        # CIF does not allow underscore in element symbol (https://www.iucr.org/__data/iucr/cifdic_html/1/cif_core.dic/Iatom_type_symbol.html)
1231        if elmsym.endswith("_"):
1232             s += PutInCol(elmsym.replace('_',''))
1233        elif '_' in elmsym:
1234             s += PutInCol(elmsym.replace('_','~'))
1235        else:
1236            s += PutInCol(elmsym,7)
1237        s += PutInCol(G2mth.ValEsd(compDict[elem],-0.009,True),5)
1238        if not quickmode:
1239            for i in 'fa','fb','fc':
1240                if i != 'fc':
1241                    for j in range(4):
1242                        if elmsym in FFtable:
1243                            val = G2mth.ValEsd(FFtable[elmsym][i][j],-0.0009,True)
1244                        else:
1245                            val = '?'
1246                        s += ' '
1247                        s += PutInCol(val,9)
1248                else:
1249                    if elmsym in FFtable:
1250                        val = G2mth.ValEsd(FFtable[elmsym][i],-0.0009,True)
1251                    else:
1252                        val = '?'
1253                    s += ' '
1254                    s += PutInCol(val,9)
1255            if elmsym in BLtable:
1256                bldata = BLtable[elmsym]
1257                #isotope = bldata[0]
1258                #mass = bldata[1]['Mass']
1259                if 'BW-LS' in bldata[1]:
1260                    val = 0
1261                else:
1262                    val = G2mth.ValEsd(bldata[1]['SL'][0],-0.0009,True)
1263            else:
1264                val = '?'
1265            s += ' '
1266            s += PutInCol(val,9)
1267            WriteCIFitem(fp,s.rstrip())
1268            WriteCIFitem(fp,'      https://subversion.xray.aps.anl.gov/pyGSAS/trunk/atmdata.py')
1269            if keV:
1270                Orbs = G2el.GetXsectionCoeff(elem.split('+')[0].split('-')[0])
1271                FP,FPP,Mu = G2el.FPcalc(Orbs, keV)
1272                WriteCIFitem(fp,{:8.3f}{:8.3f}   https://subversion.xray.aps.anl.gov/pyGSAS/trunk/atmdata.py'.format(FP,FPP))
1273        else:
1274            WriteCIFitem(fp,s.rstrip())
1275        if formula: formula += " "
1276        formula += elem
1277        if compDict[elem] == Z: continue
1278        formula += G2mth.ValEsd(compDict[elem]/Z,-0.009,True)
1279    WriteCIFitem(fp,  '\n# Note that Z affects _cell_formula_sum and _weight')
1280    WriteCIFitem(fp,  '_cell_formula_units_Z',str(Z))
1281    WriteCIFitem(fp,  '_chemical_formula_sum',formula)
1282    WriteCIFitem(fp,  '_chemical_formula_weight',
1283                  G2mth.ValEsd(cellmass/Z,-0.09,True))
1284
1285def WriteCompositionMM(fp, phasedict, phasenam, parmDict, quickmode=True, keV=None):
1286    '''determine the composition for the unit cell, crudely determine Z and
1287    then compute the composition in formula units.
1288
1289    If quickmode is False, then scattering factors are added to the element loop.
1290
1291    If keV is specified, then resonant scattering factors are also computed and included.
1292    '''
1293    General = phasedict['General']
1294    Z = General.get('cellZ',0.0)
1295    cx,ct,cs,cia = General['AtomPtrs']
1296    Atoms = phasedict['Atoms']
1297    fpfx = str(phasedict['pId'])+'::Afrac:'
1298    cfrac = cx+3
1299    cmult = cs+1
1300    compDict = {} # combines H,D & T
1301    sitemultlist = []
1302    massDict = dict(zip(General['AtomTypes'],General['AtomMass']))
1303    cellmass = 0
1304    elmLookup = {}
1305    for i,at in enumerate(Atoms):
1306        atype = at[ct].strip()
1307        if atype.find('-') != -1: atype = atype.split('-')[0]
1308        if atype.find('+') != -1: atype = atype.split('+')[0]
1309        atype = atype[0].upper()+atype[1:2].lower() # force case conversion
1310        if atype == "D" or atype == "D": atype = "H"
1311        fvar = fpfx+str(i)
1312        fval = parmDict.get(fvar,at[cfrac])
1313        mult = at[cmult]
1314        if not massDict.get(at[ct]):
1315            print('Error: No mass found for atom type '+at[ct])
1316            print('Will not compute cell contents for phase '+phasenam)
1317            return
1318        cellmass += massDict[at[ct]]*mult*fval
1319        compDict[atype] = compDict.get(atype,0.0) + mult*fval
1320        elmLookup[atype] = at[ct].strip()
1321        if fval == 1: sitemultlist.append(mult)
1322    if len(compDict.keys()) == 0: return # no elements!
1323    if Z < 1: # Z has not been computed or set by user
1324        Z = 1
1325        if not sitemultlist:
1326            General['cellZ'] = 1
1327            return
1328        for i in range(2,min(sitemultlist)+1):
1329            for m in sitemultlist:
1330                if m % i != 0:
1331                    break
1332                else:
1333                    Z = i
1334        General['cellZ'] = Z # save it
1335
1336    if not quickmode:
1337        FFtable = G2el.GetFFtable(General['AtomTypes'])
1338        BLtable = G2el.GetBLtable(General)
1339
1340    WriteCIFitem(fp, '\nloop_  _atom_site.type_symbol _atom_type.number_in_cell')
1341    s = '       '
1342    if not quickmode:
1343        for j in ('a1','a2','a3','a4','b1','b2','b3','b4','c',2,1):
1344            if len(s) > 80:
1345                WriteCIFitem(fp, s)
1346                s = '       '
1347            if j==1:
1348                s += ' _atom_type.scat_source'
1349            elif j==2:
1350                s += ' _atom_type.scat_length_neutron'
1351            else:
1352                s += ' _atom_type.scat_Cromer_Mann_'
1353                s += j
1354        if keV:
1355            WriteCIFitem(fp, s)
1356            s = '        _atom_type.scat_dispersion_real _atom_type.scat_dispersion_imag _atom_type_scat_dispersion_source'
1357        WriteCIFitem(fp, s)
1358
1359           
1360    formula = ''
1361    for elem in HillSortElements(list(compDict.keys())):
1362        s = '  '
1363        elmsym = elmLookup[elem]
1364        # CIF does not allow underscore in element symbol (https://www.iucr.org/__data/iucr/cifdic_html/1/cif_core.dic/Iatom_type_symbol.html)
1365        if elmsym.endswith("_"):
1366             s += PutInCol(elmsym.replace('_',''))
1367        elif '_' in elmsym:
1368             s += PutInCol(elmsym.replace('_','~'))
1369        else:
1370            s += PutInCol(elmsym,7)
1371        s += PutInCol(G2mth.ValEsd(compDict[elem],-0.009,True),5)
1372        if not quickmode:
1373            for i in 'fa','fb','fc':
1374                if i != 'fc':
1375                    for j in range(4):
1376                        if elmsym in FFtable:
1377                            val = G2mth.ValEsd(FFtable[elmsym][i][j],-0.0009,True)
1378                        else:
1379                            val = '?'
1380                        s += ' '
1381                        s += PutInCol(val,9)
1382                else:
1383                    if elmsym in FFtable:
1384                        val = G2mth.ValEsd(FFtable[elmsym][i],-0.0009,True)
1385                    else:
1386                        val = '?'
1387                    s += ' '
1388                    s += PutInCol(val,9)
1389            if elmsym in BLtable:
1390                bldata = BLtable[elmsym]
1391                #isotope = bldata[0]
1392                #mass = bldata[1]['Mass']
1393                if 'BW-LS' in bldata[1]:
1394                    val = 0
1395                else:
1396                    val = G2mth.ValEsd(bldata[1]['SL'][0],-0.0009,True)
1397            else:
1398                val = '?'
1399            s += ' '
1400            s += PutInCol(val,9)
1401            WriteCIFitem(fp,s.rstrip())
1402            WriteCIFitem(fp,'      https://subversion.xray.aps.anl.gov/pyGSAS/trunk/atmdata.py')
1403            if keV:
1404                Orbs = G2el.GetXsectionCoeff(elem.split('+')[0].split('-')[0])
1405                FP,FPP,Mu = G2el.FPcalc(Orbs, keV)
1406                WriteCIFitem(fp,{:8.3f}{:8.3f}   https://subversion.xray.aps.anl.gov/pyGSAS/trunk/atmdata.py'.format(FP,FPP))
1407        else:
1408            WriteCIFitem(fp,s.rstrip())
1409        if formula: formula += " "
1410        formula += elem
1411        if compDict[elem] == Z: continue
1412        formula += G2mth.ValEsd(compDict[elem]/Z,-0.009,True)
1413    WriteCIFitem(fp,  '\n# Note that Z affects _cell_formula.sum and .weight')
1414    WriteCIFitem(fp,  '_cell.formula_units_Z',str(Z))
1415    WriteCIFitem(fp,  '_chemical_formula.sum',formula)
1416    WriteCIFitem(fp,  '_chemical_formula.weight',
1417                  G2mth.ValEsd(cellmass/Z,-0.09,True))
1418
1419class ExportCIF(G2IO.ExportBaseclass):
1420    '''Base class for CIF exports
1421    '''
1422    def __init__(self,G2frame,formatName,extension,longFormatName=None,):
1423        G2IO.ExportBaseclass.__init__(self,G2frame,formatName,extension,longFormatName=None)
1424        self.exporttype = []
1425        self.author = ''
1426        self.CIFname = ''
1427
1428    def ValidateAscii(self,checklist):
1429        '''Validate items as ASCII'''
1430        msg = ''
1431        for lbl,val in checklist:
1432            if not all(ord(c) < 128 for c in val):
1433                if msg: msg += '\n'
1434                msg += lbl + " contains unicode characters: " + val
1435        if msg:
1436            G2G.G2MessageBox(self.G2frame,
1437                             'Error: CIFs can contain only ASCII characters. Please change item(s) below:\n\n'+msg,
1438                             'Unicode not valid for CIF')
1439            return True
1440
1441    def _CellSelectNeeded(self,phasenam):
1442        '''Determines if selection is needed for a T value in a multiblock CIF
1443
1444        :returns: True if the choice of T is ambiguous and a human should
1445          be asked.
1446        '''
1447        phasedict = self.Phases[phasenam] # pointer to current phase info
1448        Tlist = {}  # histname & T values used for cell w/o Hstrain
1449        DijTlist = {} # hId & T values used for cell w/Hstrain
1450        # scan over histograms used in this phase to determine the best
1451        # data collection T value
1452        for h in phasedict['Histograms']:
1453            if not phasedict['Histograms'][h]['Use']: continue
1454            if 'Flack' in phasedict['Histograms'][h].keys():       #single crystal data
1455                return False
1456            T = self.Histograms[h]['Sample Parameters']['Temperature']
1457            if np.any(abs(np.array(phasedict['Histograms'][h]['HStrain'][0])) > 1e-8):
1458                DijTlist[h] = T
1459            else:
1460                Tlist[h] = T
1461        if len(Tlist) > 0:
1462            T = sum(Tlist.values())/len(Tlist)
1463            if max(Tlist.values()) - T > 1:
1464                return True # temperatures span more than 1 degree, user needs to pick one
1465            return False
1466        elif len(DijTlist) == 1:
1467            return False
1468        elif len(DijTlist) > 1:
1469                # each histogram has different cell lengths, user needs to pick one
1470            return True
1471       
1472    def _CellSelectHist(self,phasenam):
1473        '''Select T value for a phase in a multiblock CIF
1474
1475        :returns: T,h_ranId where T is a temperature (float) or '?' and
1476          h_ranId is the random Id (ranId) for a histogram in the
1477          current phase. This is stored in OverallParms['Controls']['CellHistSelection']
1478        '''
1479        phasedict = self.Phases[phasenam] # pointer to current phase info
1480        Tlist = {}  # histname & T values used for cell w/o Hstrain
1481        DijTlist = {} # hId & T values used for cell w/Hstrain
1482        # scan over histograms used in this phase to determine the best
1483        # data collection T value
1484        for h in phasedict['Histograms']:
1485            if not phasedict['Histograms'][h]['Use']: continue
1486            if 'Flack' in phasedict['Histograms'][h].keys():       #single crystal data
1487                return (300,None)
1488            T = self.Histograms[h]['Sample Parameters']['Temperature']
1489            if np.any(abs(np.array(phasedict['Histograms'][h]['HStrain'][0])) > 1e-8):
1490                DijTlist[h] = T
1491            else:
1492                Tlist[h] = T
1493        if len(Tlist) > 0:
1494            T = sum(Tlist.values())/len(Tlist)
1495            if max(Tlist.values()) - T > 1:
1496                # temperatures span more than 1 degree, user needs to pick one
1497                choices = ["{} (unweighted average)".format(T)]
1498                Ti = [T]
1499                for h in Tlist:
1500                    choices += ["{} (hist {})".format(Tlist[h],h)]
1501                    Ti += [Tlist[h]]                           
1502                msg = 'The cell parameters for phase {} are from\nhistograms with different temperatures.\n\nSelect a T value below'.format(phasenam)
1503                dlg = wx.SingleChoiceDialog(self.G2frame,msg,'Select T',choices)
1504                if dlg.ShowModal() == wx.ID_OK:
1505                    T = Ti[dlg.GetSelection()]
1506                else:
1507                    T = '?'
1508                dlg.Destroy()
1509            return (T,None)
1510        elif len(DijTlist) == 1:
1511            h = list(DijTlist.keys())[0]
1512            h_ranId = self.Histograms[h]['ranId']
1513            return (DijTlist[h],h_ranId)
1514        elif len(DijTlist) > 1:
1515            # each histogram has different cell lengths, user needs to pick one
1516            choices = []
1517            hi = []
1518            for h in DijTlist:
1519                choices += ["{} (hist {})".format(DijTlist[h],h)]
1520                hi += [h]
1521            msg = 'There are {} sets of cell parameters for phase {}\n due to refined Hstrain values.\n\nSelect the histogram to use with the phase form list below'.format(len(DijTlist),phasenam)
1522            dlg = wx.SingleChoiceDialog(self.G2frame,msg,'Select cell',choices)
1523            if dlg.ShowModal() == wx.ID_OK:
1524                h = hi[dlg.GetSelection()] 
1525                h_ranId = self.Histograms[h]['ranId']
1526                T = DijTlist[h]
1527            else:
1528                T = '?'
1529                h_ranId = None
1530            dlg.Destroy()
1531            return (T,h_ranId)
1532        else:
1533            print('Unexpected option in _CellSelectHist for',phasenam)
1534            return ('?',None)
1535
1536    def ShowHstrainCells(self,phasenam,datablockidDict):
1537        '''Displays the unit cell parameters for phases where Dij values create
1538        mutiple sets of lattice parameters. At present there is no way defined for this in
1539        CIF, so local data names are used.
1540        '''
1541        phasedict = self.Phases[phasenam] # pointer to current phase info
1542        Tlist = {}  # histname & T values used for cell w/o Hstrain
1543        DijTlist = {} # hId & T values used for cell w/Hstrain
1544        # scan over histograms used in this phase
1545        for h in phasedict['Histograms']:
1546            if not phasedict['Histograms'][h]['Use']: continue
1547            if np.any(abs(np.array(phasedict['Histograms'][h]['HStrain'][0])) > 1e-8):
1548                DijTlist[h] = self.Histograms[h]['Sample Parameters']['Temperature']
1549            else:
1550                Tlist[h] = self.Histograms[h]['Sample Parameters']['Temperature']
1551        if len(DijTlist) == 0: return
1552        if len(Tlist) + len(DijTlist) < 2: return
1553        SGData = phasedict['General']['SGData']
1554        for i in range(len(G2py3.cellGUIlist)):
1555            if SGData['SGLaue'] in G2py3.cellGUIlist[i][0]:
1556                terms = G2py3.cellGUIlist[i][5] + [6]
1557                break
1558        else:
1559            print('ShowHstrainCells error: Laue class not found',SGData['SGLaue'])
1560            terms = list(range(7))
1561       
1562        WriteCIFitem(self.fp, '\n# cell parameters generated by hydrostatic strain')
1563        WriteCIFitem(self.fp, 'loop_')
1564        WriteCIFitem(self.fp, '\t _gsas_measurement_temperature')
1565        for i in terms:
1566            WriteCIFitem(self.fp, '\t _gsas_cell_'+cellNames[i])
1567        WriteCIFitem(self.fp, '\t _gsas_cell_histogram_blockid')
1568        for h,T in Tlist.items():
1569            pId = phasedict['pId']
1570            hId = self.Histograms[h]['hId']
1571            cellList,cellSig = G2stIO.getCellSU(pId,hId,
1572                                        phasedict['General']['SGData'],
1573                                        self.parmDict,
1574                                        self.OverallParms['Covariance'])
1575            line = '  ' + PutInCol(G2mth.ValEsd(T,-1.),6)
1576            for i in terms:
1577                line += PutInCol(G2mth.ValEsd(cellList[i],cellSig[i]),12)
1578            line += ' ' + datablockidDict[h]
1579            WriteCIFitem(self.fp, line)
1580        for h,T in DijTlist.items():
1581            pId = phasedict['pId']
1582            hId = self.Histograms[h]['hId']
1583            cellList,cellSig = G2stIO.getCellSU(pId,hId,
1584                                        phasedict['General']['SGData'],
1585                                        self.parmDict,
1586                                        self.OverallParms['Covariance'])
1587            line = '  ' + PutInCol(G2mth.ValEsd(T,-1.),6)
1588            for i in terms:
1589                line += PutInCol(G2mth.ValEsd(cellList[i],cellSig[i]),12)
1590            line += ' ' + datablockidDict[h]
1591            WriteCIFitem(self.fp, line)       
1592
1593    def _Exporter(self,event=None,phaseOnly=None,histOnly=None):
1594        '''Basic code to export a CIF. Export can be full or simple, as set by
1595        phaseOnly and histOnly which skips distances & angles, etc.
1596
1597        :param bool phaseOnly: used to export only one phase
1598        :param bool histOnly: used to export only one histogram
1599        '''
1600
1601#***** define functions for export method =======================================
1602        def WriteAudit():
1603            'Write the CIF audit values. Perhaps should be in a single element loop.'
1604            WriteCIFitem(self.fp, '_audit_creation_method',
1605                         'created in GSAS-II')
1606            WriteCIFitem(self.fp, '_audit_creation_date',self.CIFdate)
1607            if self.author:
1608                WriteCIFitem(self.fp, '_audit_author_name',self.author)
1609            WriteCIFitem(self.fp, '_audit_update_record',
1610                         self.CIFdate+'  Initial software-generated CIF')
1611
1612        def WriteOverall(mode=None):
1613            '''Write out overall refinement information.
1614
1615            More could be done here, but this is a good start.
1616            '''
1617            if self.ifPWDR:
1618                WriteCIFitem(self.fp, '_pd_proc_info_datetime', self.CIFdate)
1619                WriteCIFitem(self.fp, '_pd_calc_method', 'Rietveld Refinement')
1620               
1621            #WriteCIFitem(self.fp, '_refine_ls_shift/su_mean',DAT2)
1622            WriteCIFitem(self.fp, '_computing_structure_refinement','GSAS-II (Toby & Von Dreele, J. Appl. Cryst. 46, 544-549, 2013)')
1623            if self.ifHKLF:
1624                controls = self.OverallParms['Controls']
1625                try:
1626                    if controls['F**2']:
1627                        thresh = 'F**2>%.1fu(F**2)'%(controls['UsrReject']['minF/sig'])
1628                    else:
1629                        thresh = 'F>%.1fu(F)'%(controls['UsrReject']['minF/sig'])
1630                    WriteCIFitem(self.fp, '_reflns_threshold_expression', thresh)
1631                except KeyError:
1632                    pass
1633            WriteCIFitem(self.fp, '_refine_ls_matrix_type','full')
1634
1635            if mode == 'seq': return
1636            try:
1637                vars = str(len(self.OverallParms['Covariance']['varyList']))
1638            except:
1639                vars = '?'
1640            WriteCIFitem(self.fp, '_refine_ls_number_parameters',vars)
1641            try:
1642                GOF = G2mth.ValEsd(self.OverallParms['Covariance']['Rvals']['GOF'],-0.009)
1643            except:
1644                GOF = '?'
1645            WriteCIFitem(self.fp, '_refine_ls_goodness_of_fit_all',GOF)
1646            DAT1 = self.OverallParms['Covariance']['Rvals'].get('Max shft/sig',0.0)
1647            if DAT1:
1648                WriteCIFitem(self.fp, '_refine_ls_shift/su_max','%.4f'%DAT1)
1649
1650            # get restraint info
1651            # restraintDict = self.OverallParms.get('Restraints',{})
1652            # for i in  self.OverallParms['Constraints']:
1653            #     print i
1654            #     for j in self.OverallParms['Constraints'][i]:
1655            #         print j
1656            #WriteCIFitem(self.fp, '_refine_ls_number_restraints',TEXT)
1657            # other things to consider reporting
1658            # _refine_ls_number_reflns
1659            # _refine_ls_goodness_of_fit_obs
1660            # _refine_ls_wR_factor_obs
1661            # _refine_ls_restrained_S_all
1662            # _refine_ls_restrained_S_obs
1663
1664            # include an overall profile r-factor, if there is more than one powder histogram
1665            R = '%.5f'%(self.OverallParms['Covariance']['Rvals']['Rwp']/100.)
1666            WriteCIFitem(self.fp, '\n# OVERALL WEIGHTED R-FACTOR')
1667            WriteCIFitem(self.fp, '_refine_ls_wR_factor_obs',R)
1668                # _refine_ls_R_factor_all
1669                # _refine_ls_R_factor_obs
1670            #WriteCIFitem(self.fp, '_refine_ls_matrix_type','userblocks')
1671
1672        def writeCIFtemplate(G2dict,tmplate,defaultname=''):
1673            '''Write out the selected or edited CIF template
1674            An unedited CIF template file is copied, comments intact; an edited
1675            CIF template is written out from PyCifRW which of course strips comments.
1676            In all cases the initial data_ header is stripped (there should only be one!)
1677            '''
1678            CIFobj = G2dict.get("CIF_template")
1679            if defaultname:
1680                defaultname = G2obj.StripUnicode(defaultname)
1681                defaultname = re.sub(r'[^a-zA-Z0-9_-]','',defaultname)
1682                defaultname = tmplate + "_" + defaultname + ".cif"
1683            else:
1684                defaultname = ''
1685            templateDefName = 'template_'+tmplate+'.cif'
1686            if not CIFobj: # copying a template
1687                for pth in [os.getcwd()]+sys.path:
1688                    fil = os.path.join(pth,defaultname)
1689                    if os.path.exists(fil) and defaultname: break
1690                else:
1691                    for pth in sys.path:
1692                        fil = os.path.join(pth,templateDefName)
1693                        if os.path.exists(fil): break
1694                    else:
1695                        print(CIFobj+' not found in path!')
1696                        return
1697                fp = open(fil,'r')
1698                txt = fp.read()
1699                fp.close()
1700            elif type(CIFobj) is not list and type(CIFobj) is not tuple:
1701                if not os.path.exists(CIFobj):
1702                    print("Error: requested template file has disappeared: "+CIFobj)
1703                    return
1704                fp = open(CIFobj,'r')
1705                txt = fp.read()
1706                fp.close()
1707            else:
1708                txt = dict2CIF(CIFobj[0],CIFobj[1]).WriteOut()
1709            # remove the PyCifRW header, if present
1710            #if txt.find('PyCifRW') > -1 and txt.find('data_') > -1:
1711            txt = "# GSAS-II edited template follows "+txt[txt.index("data_")+5:]
1712            #txt = txt.replace('data_','#')
1713            WriteCIFitem(self.fp, txt)
1714
1715        def FormatSH(phasenam):
1716            'Format a full spherical harmonics texture description as a string'
1717            phasedict = self.Phases[phasenam] # pointer to current phase info
1718            pfx = str(phasedict['pId'])+'::'
1719            s = ""
1720            textureData = phasedict['General']['SH Texture']
1721            if textureData.get('Order'):
1722                s += "Spherical Harmonics correction. Order = "+str(textureData['Order'])
1723                s += " Model: " + str(textureData['Model']) + "\n    Orientation angles: "
1724                for name in ['omega','chi','phi']:
1725                    aname = pfx+'SH '+name
1726                    s += name + " = "
1727                    sig = self.sigDict.get(aname,-0.09)
1728                    s += G2mth.ValEsd(self.parmDict[aname],sig)
1729                    s += "; "
1730                s += "\n"
1731                s1 = "    Coefficients:  "
1732                for name in textureData['SH Coeff'][1]:
1733                    aname = pfx+name
1734                    if len(s1) > 60:
1735                        s += s1 + "\n"
1736                        s1 = "    "
1737                    s1 += aname + ' = '
1738                    sig = self.sigDict.get(aname,-0.0009)
1739                    s1 += G2mth.ValEsd(self.parmDict[aname],sig)
1740                    s1 += "; "
1741                s += s1
1742            return s
1743
1744        def FormatHAPpo(phasenam):
1745            '''return the March-Dollase/SH correction for every
1746            histogram in the current phase formatted into a
1747            character string
1748            '''
1749            phasedict = self.Phases[phasenam] # pointer to current phase info
1750            s = ''
1751            for histogram in sorted(phasedict['Histograms']):
1752                if histogram.startswith("HKLF"): continue # powder only
1753                if not self.Phases[phasenam]['Histograms'][histogram]['Use']: continue
1754                Histogram = self.Histograms.get(histogram)
1755                if not Histogram: continue
1756                hapData = phasedict['Histograms'][histogram]
1757                if hapData['Pref.Ori.'][0] == 'MD':
1758                    aname = str(phasedict['pId'])+':'+str(Histogram['hId'])+':MD'
1759                    if self.parmDict.get(aname,1.0) != 1.0: continue
1760                    sig = self.sigDict.get(aname,-0.009)
1761                    if s != "": s += '\n'
1762                    s += 'March-Dollase correction'
1763                    if len(self.powderDict) > 1:
1764                        s += ', histogram '+str(Histogram['hId']+1)
1765                    s += ' coef. = ' + G2mth.ValEsd(self.parmDict[aname],sig)
1766                    s += ' axis = ' + str(hapData['Pref.Ori.'][3])
1767                else: # must be SH
1768                    if s != "": s += '\n'
1769                    s += 'Simple spherical harmonic correction'
1770                    if len(self.powderDict) > 1:
1771                        s += ', histogram '+str(Histogram['hId']+1)
1772                    s += ' Order = '+str(hapData['Pref.Ori.'][4])+'\n'
1773                    s1 = "    Coefficients:  "
1774                    for item in hapData['Pref.Ori.'][5]:
1775                        aname = str(phasedict['pId'])+':'+str(Histogram['hId'])+':'+item
1776                        if len(s1) > 60:
1777                            s += s1 + "\n"
1778                            s1 = "    "
1779                        s1 += aname + ' = '
1780                        sig = self.sigDict.get(aname,-0.0009)
1781                        s1 += G2mth.ValEsd(self.parmDict[aname],sig)
1782                        s1 += "; "
1783                    s += s1
1784            return s
1785
1786        def FormatBackground(bkg,hId):
1787            '''Display the Background information as a descriptive text string.
1788
1789            TODO: this needs to be expanded to show the diffuse peak and
1790            Debye term information as well. (Bob)
1791
1792            :returns: the text description (str)
1793            '''
1794            hfx = ':'+str(hId)+':'
1795            fxn, bkgdict = bkg
1796            terms = fxn[2]
1797            txt = 'Background function: "'+fxn[0]+'" function with '+str(terms)+' terms:\n'
1798            l = "    "
1799            for i,v in enumerate(fxn[3:]):
1800                name = '%sBack;%d'%(hfx,i)
1801                sig = self.sigDict.get(name,-0.009)
1802                if len(l) > 60:
1803                    txt += l + '\n'
1804                    l = '    '
1805                l += G2mth.ValEsd(v,sig)+', '
1806            txt += l
1807            if bkgdict['nDebye']:
1808                txt += '\n  Background Debye function parameters: A, R, U:'
1809                names = ['A;','R;','U;']
1810                for i in range(bkgdict['nDebye']):
1811                    txt += '\n    '
1812                    for j in range(3):
1813                        name = hfx+'Debye'+names[j]+str(i)
1814                        sig = self.sigDict.get(name,-0.009)
1815                        txt += G2mth.ValEsd(bkgdict['debyeTerms'][i][2*j],sig)+', '
1816            if bkgdict['nPeaks']:
1817                txt += '\n  Background peak parameters: pos, int, sig, gam:'
1818                names = ['pos;','int;','sig;','gam;']
1819                for i in range(bkgdict['nPeaks']):
1820                    txt += '\n    '
1821                    for j in range(4):
1822                        name = hfx+'BkPk'+names[j]+str(i)
1823                        sig = self.sigDict.get(name,-0.009)
1824                        txt += G2mth.ValEsd(bkgdict['peaksList'][i][2*j],sig)+', '
1825            return txt
1826
1827        def FormatInstProfile(instparmdict,hId):
1828            '''Format the instrumental profile parameters with a
1829            string description. Will only be called on PWDR histograms
1830            '''
1831            s = ''
1832            inst = instparmdict[0]
1833            hfx = ':'+str(hId)+':'
1834            if 'C' in inst['Type'][0]:
1835                s = 'Finger-Cox-Jephcoat function parameters U, V, W, X, Y, SH/L:\n'
1836                s += '  peak variance(Gauss) = Utan(Th)^2^+Vtan(Th)+W:\n'
1837                s += '  peak HW(Lorentz) = X/cos(Th)+Ytan(Th); SH/L = S/L+H/L\n'
1838                s += '  U, V, W in (centideg)^2^, X & Y in centideg\n    '
1839                for item in ['U','V','W','X','Y','SH/L']:
1840                    name = hfx+item
1841                    sig = self.sigDict.get(name,-0.009)
1842                    s += G2mth.ValEsd(inst[item][1],sig)+', '
1843            elif 'T' in inst['Type'][0]:    #to be tested after TOF Rietveld done
1844                s = 'Von Dreele-Jorgenson-Windsor function parameters\n'+ \
1845                    '   alpha, beta-0, beta-1, beta-q, sig-0, sig-1, sig-2, sig-q, X, Y:\n    '
1846                for item in ['alpha','beta-0','beta-1','beta-q','sig-0','sig-1','sig-2','sig-q','X','Y']:
1847                    name = hfx+item
1848                    sig = self.sigDict.get(name,-0.009)
1849                    s += G2mth.ValEsd(inst[item][1],sig)+', '
1850            return s
1851
1852        def FormatPhaseProfile(phasenam,hist=''):
1853            '''Format the phase-related profile parameters (size/strain)
1854            with a string description.
1855            return an empty string or None if there are no
1856            powder histograms for this phase.
1857            '''
1858            s = ''
1859            phasedict = self.Phases[phasenam] # pointer to current phase info
1860            if hist:
1861                parmDict = self.seqData[hist]['parmDict']
1862                sigDict = dict(zip(self.seqData[hist]['varyList'],self.seqData[hist]['sig']))
1863            else:
1864                parmDict = self.parmDict
1865                sigDict = self.sigDict
1866           
1867            SGData = phasedict['General'] ['SGData']
1868            for histogram in sorted(phasedict['Histograms']):
1869                if hist is not None and hist != histogram: continue
1870                if histogram.startswith("HKLF"): continue # powder only
1871                Histogram = self.Histograms.get(histogram)
1872                if not Histogram: continue
1873                hapData = phasedict['Histograms'][histogram]
1874                pId = phasedict['pId']
1875                hId = Histogram['hId']
1876                phfx = '%d:%d:'%(pId,hId)
1877                size = hapData['Size']
1878                mustrain = hapData['Mustrain']
1879                hstrain = hapData['HStrain']
1880                if s: s += '\n'
1881                if len(self.powderDict) > 1: # if one histogram, no ambiguity
1882                    s += '  Parameters for histogram #{:} {:} & phase {:}\n'.format(
1883                        str(hId),str(histogram),phasenam)
1884                s += '  Crystallite size in microns with "%s" model:\n  '%(size[0])
1885                names = ['Size;i','Size;mx']
1886                if 'uniax' in size[0]:
1887                    names = ['Size;i','Size;a','Size;mx']
1888                    s += 'anisotropic axis is %s\n  '%(str(size[3]))
1889                    s += 'parameters: equatorial size, axial size, G/L mix\n    '
1890                    for i,item in enumerate(names):
1891                        name = phfx+item
1892                        val = parmDict.get(name,size[1][i])
1893                        sig = sigDict.get(name,-0.009)
1894                        s += G2mth.ValEsd(val,sig)+', '
1895                elif 'ellip' in size[0]:
1896                    s += 'parameters: S11, S22, S33, S12, S13, S23, G/L mix\n    '
1897                    for i in range(6):
1898                        name = phfx+'Size:'+str(i)
1899                        val = parmDict.get(name,size[4][i])
1900                        sig = sigDict.get(name,-0.009)
1901                        s += G2mth.ValEsd(val,sig)+', '
1902                    sig = sigDict.get(phfx+'Size;mx',-0.009)
1903                    s += G2mth.ValEsd(size[1][2],sig)+', '
1904                else:       #isotropic
1905                    s += 'parameters: Size, G/L mix\n    '
1906                    i = 0
1907                    for item in names:
1908                        name = phfx+item
1909                        val = parmDict.get(name,size[1][i])
1910                        sig = sigDict.get(name,-0.009)
1911                        s += G2mth.ValEsd(val,sig)+', '
1912                        i = 2    #skip the aniso value
1913                s += '\n  Microstrain, "%s" model (10^6^ * delta Q/Q)\n  '%(mustrain[0])
1914                names = ['Mustrain;i','Mustrain;mx']
1915                if 'uniax' in mustrain[0]:
1916                    names = ['Mustrain;i','Mustrain;a','Mustrain;mx']
1917                    s += 'anisotropic axis is %s\n  '%(str(size[3]))
1918                    s += 'parameters: equatorial mustrain, axial mustrain, G/L mix\n    '
1919                    for i,item in enumerate(names):
1920                        name = phfx+item
1921                        val = parmDict.get(name,mustrain[1][i])
1922                        sig = sigDict.get(name,-0.009)
1923                        s += G2mth.ValEsd(val,sig)+', '
1924                elif 'general' in mustrain[0]:
1925                    names = 'parameters: '
1926                    for i,name in enumerate(G2spc.MustrainNames(SGData)):
1927                        names += name+', '
1928                        if i == 9:
1929                            names += '\n  '
1930                    names += 'G/L mix\n    '
1931                    s += names
1932                    txt = ''
1933                    for i in range(len(mustrain[4])):
1934                        name = phfx+'Mustrain:'+str(i)
1935                        val = parmDict.get(name,mustrain[4][i])
1936                        sig = sigDict.get(name,-0.009)
1937                        if len(txt) > 60:
1938                            s += txt+'\n    '
1939                            txt = ''
1940                        txt += G2mth.ValEsd(val,sig)+', '
1941                    s += txt
1942                    name = phfx+'Mustrain;mx'
1943                    val = parmDict.get(name,mustrain[1][2])
1944                    sig = sigDict.get(name,-0.009)
1945                    s += G2mth.ValEsd(val,sig)+', '
1946
1947                else:       #isotropic
1948                    s += '  parameters: Mustrain, G/L mix\n    '
1949                    i = 0
1950                    for item in names:
1951                        name = phfx+item
1952                        val = parmDict.get(name,mustrain[1][i])
1953                        sig = sigDict.get(name,-0.009)
1954                        s += G2mth.ValEsd(val,sig)+', '
1955                        i = 2    #skip the aniso value
1956                s1 = \n  Macrostrain parameters: '
1957                names = G2spc.HStrainNames(SGData)
1958                for name in names:
1959                    s1 += name+', '
1960                s1 += '\n    '
1961                macrostrain = False
1962                for i in range(len(names)):
1963                    name = phfx+names[i]
1964                    val = parmDict.get(name,hstrain[0][i])
1965                    sig = sigDict.get(name,-0.000009)
1966                    s1 += G2mth.ValEsd(val,sig)+', '
1967                    if hstrain[0][i]: macrostrain = True
1968                if macrostrain:
1969                    s += s1 + '\n'
1970                    # show revised lattice parameters here someday
1971                else:
1972                    s += '\n'
1973            return s
1974
1975        def MakeUniqueLabel(lbl,labellist):
1976            'Make sure that every atom label is unique'
1977            lbl = lbl.strip()
1978            if not lbl: # deal with a blank label
1979                lbl = 'A_1'
1980            if lbl not in labellist:
1981                labellist.append(lbl)
1982                return lbl
1983            i = 1
1984            prefix = lbl
1985            if '_' in lbl:
1986                prefix = lbl[:lbl.rfind('_')]
1987                suffix = lbl[lbl.rfind('_')+1:]
1988                try:
1989                    i = int(suffix)+1
1990                except:
1991                    pass
1992            while prefix+'_'+str(i) in labellist:
1993                i += 1
1994            else:
1995                lbl = prefix+'_'+str(i)
1996                labellist.append(lbl)
1997
1998        def WriteDistances(phasenam):
1999            '''Report bond distances and angles for the CIF
2000
2001            Note that _geom_*_symmetry_* fields are values of form
2002            n_klm where n is the symmetry operation in SymOpList (counted
2003            starting with 1) and (k-5, l-5, m-5) are translations to add
2004            to (x,y,z). See
2005            http://www.iucr.org/__data/iucr/cifdic_html/1/cif_core.dic/Igeom_angle_site_symmetry_.html
2006
2007            TODO: need a method to select publication flags for distances/angles
2008            '''
2009            phasedict = self.Phases[phasenam] # pointer to current phase info
2010            Atoms = phasedict['Atoms']
2011            generalData = phasedict['General']
2012            # create a dict for storing Pub flag for bonds/angles, if needed
2013            if phasedict['General'].get("DisAglHideFlag") is None:
2014                phasedict['General']["DisAglHideFlag"] = {}
2015            DisAngSel = phasedict['General']["DisAglHideFlag"]
2016            cx,ct,cs,cia = phasedict['General']['AtomPtrs']
2017            cn = ct-1
2018            fpfx = str(phasedict['pId'])+'::Afrac:'
2019            cfrac = cx+3
2020            DisAglData = {}
2021            # create a list of atoms, but skip atoms with zero occupancy
2022            xyz = []
2023            fpfx = str(phasedict['pId'])+'::Afrac:'
2024            for i,atom in enumerate(Atoms):
2025                if self.parmDict.get(fpfx+str(i),atom[cfrac]) == 0.0: continue
2026                xyz.append([i,]+atom[cn:cn+2]+atom[cx:cx+3])
2027            if 'DisAglCtls' not in generalData:
2028                # should not happen, since DisAglDialog should be called
2029                # for all phases before getting here
2030                dlg = G2G.DisAglDialog(
2031                    self.G2frame,
2032                    {},
2033                    generalData)
2034                if dlg.ShowModal() == wx.ID_OK:
2035                    generalData['DisAglCtls'] = dlg.GetData()
2036                else:
2037                    dlg.Destroy()
2038                    return
2039                dlg.Destroy()
2040            DisAglData['OrigAtoms'] = xyz
2041            DisAglData['TargAtoms'] = xyz
2042            SymOpList,offsetList,symOpList,G2oprList,G2opcodes = G2spc.AllOps(
2043                generalData['SGData'])
2044
2045#            xpandSGdata = generalData['SGData'].copy()
2046#            xpandSGdata.update({'SGOps':symOpList,
2047#                                'SGInv':False,
2048#                                'SGLatt':'P',
2049#                                'SGCen':np.array([[0, 0, 0]]),})
2050#            DisAglData['SGData'] = xpandSGdata
2051            DisAglData['SGData'] = generalData['SGData'].copy()
2052
2053            DisAglData['Cell'] = generalData['Cell'][1:] #+ volume
2054            if 'pId' in phasedict:
2055                DisAglData['pId'] = phasedict['pId']
2056                DisAglData['covData'] = self.OverallParms['Covariance']
2057            try:
2058                AtomLabels,DistArray,AngArray = G2stMn.RetDistAngle(
2059                    generalData['DisAglCtls'],
2060                    DisAglData)
2061            except KeyError:        # inside DistAngle for missing atom types in DisAglCtls
2062                print(u'**** ERROR computing distances & angles for phase {} ****\nresetting to default values'.format(phasenam))
2063                data = generalData['DisAglCtls'] = {}
2064                data['Name'] = generalData['Name']
2065                data['Factors'] = [0.85,0.85]
2066                data['AtomTypes'] = generalData['AtomTypes']
2067                data['BondRadii'] = generalData['BondRadii'][:]
2068                data['AngleRadii'] = generalData['AngleRadii'][:]
2069                try:
2070                    AtomLabels,DistArray,AngArray = G2stMn.RetDistAngle(
2071                        generalData['DisAglCtls'],
2072                        DisAglData)
2073                except:
2074                    print('Reset failed. To fix this, use the Reset button in the "edit distance/angle menu" for this phase')
2075                    return
2076
2077            # loop over interatomic distances for this phase
2078            WriteCIFitem(self.fp, '\n# MOLECULAR GEOMETRY')
2079            First = True
2080            for i in sorted(AtomLabels.keys()):
2081                Dist = DistArray[i]
2082                for D in Dist:
2083                    line = '  '+PutInCol(AtomLabels[i],6)+PutInCol(AtomLabels[D[0]],6)
2084                    sig = D[4]
2085                    if sig == 0: sig = -0.00009
2086                    line += PutInCol(G2mth.ValEsd(D[3],sig,True),10)
2087                    line += "  1_555 "
2088                    symopNum = G2opcodes.index(D[2])
2089                    line += " {:3d}_".format(symopNum+1)
2090                    for d,o in zip(D[1],offsetList[symopNum]):
2091                        line += "{:1d}".format(d-o+5)
2092                    if DisAngSel.get((i,tuple(D[0:3]))):
2093                        line += " no"
2094                    else:
2095                        line += " yes"
2096                    if First:
2097                        First = False
2098                        WriteCIFitem(self.fp, 'loop_' +
2099                         '\n   _geom_bond_atom_site_label_1' +
2100                         '\n   _geom_bond_atom_site_label_2' +
2101                         '\n   _geom_bond_distance' +
2102                         '\n   _geom_bond_site_symmetry_1' +
2103                         '\n   _geom_bond_site_symmetry_2' +
2104                         '\n   _geom_bond_publ_flag')
2105                    WriteCIFitem(self.fp, line)
2106
2107            # loop over interatomic angles for this phase
2108            First = True
2109            for i in sorted(AtomLabels.keys()):
2110                Dist = DistArray[i]
2111                for k,j,tup in AngArray[i]:
2112                    Dj = Dist[j]
2113                    Dk = Dist[k]
2114                    line = '  '+PutInCol(AtomLabels[Dj[0]],6)+PutInCol(AtomLabels[i],6)+PutInCol(AtomLabels[Dk[0]],6)
2115                    sig = tup[1]
2116                    if sig == 0: sig = -0.009
2117                    line += PutInCol(G2mth.ValEsd(tup[0],sig,True),10)
2118                    line += " {:3d}_".format(G2opcodes.index(Dj[2])+1)
2119                    for d in Dj[1]:
2120                        line += "{:1d}".format(d+5)
2121                    line += "  1_555 "
2122                    line += " {:3d}_".format(G2opcodes.index(Dk[2])+1)
2123                    for d in Dk[1]:
2124                        line += "{:1d}".format(d+5)
2125                    key = (tuple(Dk[0:3]),i,tuple(Dj[0:3]))
2126                    if DisAngSel.get(key):
2127                        line += " no"
2128                    else:
2129                        line += " yes"
2130                    if First:
2131                        First = False
2132                        WriteCIFitem(self.fp, '\nloop_' +
2133                         '\n   _geom_angle_atom_site_label_1' +
2134                         '\n   _geom_angle_atom_site_label_2' +
2135                         '\n   _geom_angle_atom_site_label_3' +
2136                         '\n   _geom_angle' +
2137                         '\n   _geom_angle_site_symmetry_1' +
2138                         '\n   _geom_angle_site_symmetry_2' +
2139                         '\n   _geom_angle_site_symmetry_3' +
2140                         '\n   _geom_angle_publ_flag')
2141                    WriteCIFitem(self.fp, line)
2142
2143
2144        def WriteSeqDistances(phasenam,histname,phasedict,cellList,seqData):
2145            '''Report bond distances and angles for the CIF from a Sequential fit
2146
2147            Note that _geom_*_symmetry_* fields are values of form
2148            n_klm where n is the symmetry operation in SymOpList (counted
2149            starting with 1) and (k-5, l-5, m-5) are translations to add
2150            to (x,y,z). See
2151            http://www.iucr.org/__data/iucr/cifdic_html/1/cif_core.dic/Igeom_angle_site_symmetry_.html
2152
2153            TODO: this is based on WriteDistances and could likely be merged with that
2154            without too much work. Note also that G2stMn.RetDistAngle is pretty slow for
2155            sequential fits, since it is called so many times.
2156            '''
2157            #breakpoint()
2158            Atoms = phasedict['Atoms']
2159            generalData = phasedict['General']
2160            parmDict = seqData[histname]['parmDict']
2161#            sigDict = dict(zip(seqData[hist]['varyList'],seqData[hist]['sig']))
2162            # create a dict for storing Pub flag for bonds/angles, if needed
2163            if phasedict['General'].get("DisAglHideFlag") is None:
2164                phasedict['General']["DisAglHideFlag"] = {}
2165            DisAngSel = phasedict['General']["DisAglHideFlag"]
2166            cx,ct,cs,cia = phasedict['General']['AtomPtrs']
2167            cn = ct-1
2168#            fpfx = str(phasedict['pId'])+'::Afrac:'
2169            cfrac = cx+3
2170            DisAglData = {}
2171            # create a list of atoms, but skip atoms with zero occupancy
2172            xyz = []
2173            fpfx = str(phasedict['pId'])+'::Afrac:'
2174            for i,atom in enumerate(Atoms):
2175                if parmDict.get(fpfx+str(i),atom[cfrac]) == 0.0: continue
2176                thisatom = [i] + atom[cn:cn+2]
2177                for j,lab in enumerate(['x','y','z']):
2178                    xyzkey = str(phasedict['pId'])+'::A'+ lab + ':' +str(i)
2179                    thisatom.append(parmDict.get(xyzkey,atom[cx+j]))
2180                xyz.append(thisatom)
2181            DisAglData['OrigAtoms'] = xyz
2182            DisAglData['TargAtoms'] = xyz
2183            SymOpList,offsetList,symOpList,G2oprList,G2opcodes = G2spc.AllOps(
2184                generalData['SGData'])
2185
2186#            xpandSGdata = generalData['SGData'].copy()
2187#            xpandSGdata.update({'SGOps':symOpList,
2188#                                'SGInv':False,
2189#                                'SGLatt':'P',
2190#                                'SGCen':np.array([[0, 0, 0]]),})
2191#            DisAglData['SGData'] = xpandSGdata
2192            DisAglData['SGData'] = generalData['SGData'].copy()
2193
2194            DisAglData['Cell'] = cellList  #+ volume
2195            if 'pId' in phasedict:
2196                DisAglData['pId'] = phasedict['pId']
2197                DisAglData['covData'] = seqData[histname]
2198                # self.OverallParms['Covariance']
2199            try:
2200                AtomLabels,DistArray,AngArray = G2stMn.RetDistAngle(
2201                    generalData['DisAglCtls'],
2202                    DisAglData)
2203            except KeyError:        # inside DistAngle for missing atom types in DisAglCtls
2204                print(u'**** ERROR computing distances & angles for phase {} ****\nresetting to default values'.format(phasenam))
2205                data = generalData['DisAglCtls'] = {}
2206                data['Name'] = generalData['Name']
2207                data['Factors'] = [0.85,0.85]
2208                data['AtomTypes'] = generalData['AtomTypes']
2209                data['BondRadii'] = generalData['BondRadii'][:]
2210                data['AngleRadii'] = generalData['AngleRadii'][:]
2211                try:
2212                    AtomLabels,DistArray,AngArray = G2stMn.RetDistAngle(
2213                        generalData['DisAglCtls'],
2214                        DisAglData)
2215                except:
2216                    print('Reset failed. To fix this, use the Reset button in the "edit distance/angle menu" for this phase')
2217                    return
2218
2219            # loop over interatomic distances for this phase
2220            WriteCIFitem(self.fp, '\n# MOLECULAR GEOMETRY')
2221            First = True
2222            for i in sorted(AtomLabels.keys()):
2223                Dist = DistArray[i]
2224                for D in Dist:
2225                    line = '  '+PutInCol(AtomLabels[i],6)+PutInCol(AtomLabels[D[0]],6)
2226                    sig = D[4]
2227                    if sig == 0: sig = -0.00009
2228                    line += PutInCol(G2mth.ValEsd(D[3],sig,True),10)
2229                    line += "  1_555 "
2230                    symopNum = G2opcodes.index(D[2])
2231                    line += " {:3d}_".format(symopNum+1)
2232                    for d,o in zip(D[1],offsetList[symopNum]):
2233                        line += "{:1d}".format(d-o+5)
2234                    if DisAngSel.get((i,tuple(D[0:3]))):
2235                        line += " no"
2236                    else:
2237                        line += " yes"
2238                    if First:
2239                        First = False
2240                        WriteCIFitem(self.fp, 'loop_' +
2241                         '\n   _geom_bond_atom_site_label_1' +
2242                         '\n   _geom_bond_atom_site_label_2' +
2243                         '\n   _geom_bond_distance' +
2244                         '\n   _geom_bond_site_symmetry_1' +
2245                         '\n   _geom_bond_site_symmetry_2' +
2246                         '\n   _geom_bond_publ_flag')
2247                    WriteCIFitem(self.fp, line)
2248
2249            # loop over interatomic angles for this phase
2250            First = True
2251            for i in sorted(AtomLabels.keys()):
2252                Dist = DistArray[i]
2253                for k,j,tup in AngArray[i]:
2254                    Dj = Dist[j]
2255                    Dk = Dist[k]
2256                    line = '  '+PutInCol(AtomLabels[Dj[0]],6)+PutInCol(AtomLabels[i],6)+PutInCol(AtomLabels[Dk[0]],6)
2257                    sig = tup[1]
2258                    if sig == 0: sig = -0.009
2259                    line += PutInCol(G2mth.ValEsd(tup[0],sig,True),10)
2260                    line += " {:3d}_".format(G2opcodes.index(Dj[2])+1)
2261                    for d in Dj[1]:
2262                        line += "{:1d}".format(d+5)
2263                    line += "  1_555 "
2264                    line += " {:3d}_".format(G2opcodes.index(Dk[2])+1)
2265                    for d in Dk[1]:
2266                        line += "{:1d}".format(d+5)
2267                    key = (tuple(Dk[0:3]),i,tuple(Dj[0:3]))
2268                    if DisAngSel.get(key):
2269                        line += " no"
2270                    else:
2271                        line += " yes"
2272                    if First:
2273                        First = False
2274                        WriteCIFitem(self.fp, '\nloop_' +
2275                         '\n   _geom_angle_atom_site_label_1' +
2276                         '\n   _geom_angle_atom_site_label_2' +
2277                         '\n   _geom_angle_atom_site_label_3' +
2278                         '\n   _geom_angle' +
2279                         '\n   _geom_angle_site_symmetry_1' +
2280                         '\n   _geom_angle_site_symmetry_2' +
2281                         '\n   _geom_angle_site_symmetry_3' +
2282                         '\n   _geom_angle_publ_flag')
2283                    WriteCIFitem(self.fp, line)
2284
2285        def WriteSeqOverallPhaseInfo(phasenam,histblk):
2286            'Write out the phase information for the selected phase for the overall block in a sequential fit'
2287            WriteCIFitem(self.fp, '# overall phase info for '+str(phasenam) + ' follows')
2288            phasedict = self.Phases[phasenam] # pointer to current phase info
2289            WriteCIFitem(self.fp, '_pd_phase_name', phasenam)
2290
2291            WriteCIFitem(self.fp, '_symmetry_cell_setting',
2292                         phasedict['General']['SGData']['SGSys'])
2293
2294            if phasedict['General']['Type'] in ['nuclear','macromolecular']:
2295                spacegroup = phasedict['General']['SGData']['SpGrp'].strip()
2296                # regularize capitalization and remove trailing H/R
2297                spacegroup = spacegroup[0].upper() + spacegroup[1:].lower().rstrip('rh ')
2298                WriteCIFitem(self.fp, '_symmetry_space_group_name_H-M',spacegroup)
2299   
2300                # generate symmetry operations including centering and center of symmetry
2301                SymOpList,offsetList,symOpList,G2oprList,G2opcodes = G2spc.AllOps(
2302                    phasedict['General']['SGData'])
2303                WriteCIFitem(self.fp, 'loop_\n    _space_group_symop_id\n    _space_group_symop_operation_xyz')
2304                for i,op in enumerate(SymOpList,start=1):
2305                    WriteCIFitem(self.fp, '   {:3d}  {:}'.format(i,op.lower()))
2306            elif phasedict['General']['Type'] == 'magnetic':
2307                parentSpGrp = phasedict['General']['SGData']['SpGrp'].strip()
2308                parentSpGrp = parentSpGrp[0].upper() + parentSpGrp[1:].lower().rstrip('rh ')
2309                WriteCIFitem(self.fp, '_parent_space_group.name_H-M_alt',parentSpGrp)
2310#                [Trans,Uvec,Vvec] = phasedict['General']['SGData']['fromParent']         #save these
2311                spacegroup = phasedict['General']['SGData']['MagSpGrp'].strip()
2312                spacegroup = spacegroup[0].upper() + spacegroup[1:].lower().rstrip('rh ')
2313                WriteCIFitem(self.fp, '_space_group_magn.name_BNS',spacegroup)
2314                WriteCIFitem(self.fp, '_space_group.magn_point_group',phasedict['General']['SGData']['MagPtGp'])
2315
2316                # generate symmetry operations including centering and center of symmetry
2317                SymOpList,offsetList,symOpList,G2oprList,G2opcodes = G2spc.AllOps(
2318                    phasedict['General']['SGData'])
2319                SpnFlp = phasedict['General']['SGData']['SpnFlp']
2320                WriteCIFitem(self.fp, 'loop_\n    _space_group_symop_magn_operation.id\n    _space_group_symop_magn_operation.xyz')
2321                for i,op in enumerate(SymOpList,start=1):
2322                    if SpnFlp[i-1] >0:
2323                        opr = op.lower()+',+1'
2324                    else:
2325                        opr = op.lower()+',-1'
2326                    WriteCIFitem(self.fp, '   {:3d}  {:}'.format(i,opr))
2327                   
2328            lam = None
2329            if 'X' in histblk['Instrument Parameters'][0]['Type'][0]:
2330                for k in ('Lam','Lam1'):
2331                    if k in histblk['Instrument Parameters'][0]:
2332                        lam = histblk['Instrument Parameters'][0][k][0]
2333                        break
2334            keV = None
2335            if lam: keV = 12.397639/lam
2336            # report cell contents                       
2337            WriteComposition(self.fp, self.Phases[phasenam], phasenam, self.parmDict, False, keV)
2338       
2339        def WriteSeqPhaseVals(phasenam,phasedict,pId,histname):
2340            'Write out the phase information for the selected phase'
2341            WriteCIFitem(self.fp, '_pd_phase_name', phasenam)
2342            cellList,cellSig = getCellwStrain(phasedict,self.seqData,pId,histname)
2343            T = self.Histograms[histname]['Sample Parameters']['Temperature']
2344            try:
2345                T = G2mth.ValEsd(T,-1.0)
2346            except:
2347                pass
2348            WriteCIFitem(self.fp, '_symmetry_cell_setting',
2349                         phasedict['General']['SGData']['SGSys'])
2350
2351            if phasedict['General']['Type'] in ['nuclear','macromolecular']:
2352                spacegroup = phasedict['General']['SGData']['SpGrp'].strip()
2353                # regularize capitalization and remove trailing H/R
2354                spacegroup = spacegroup[0].upper() + spacegroup[1:].lower().rstrip('rh ')
2355                WriteCIFitem(self.fp, '_symmetry_space_group_name_H-M',spacegroup)
2356            WriteCIFitem(self.fp,"_cell_measurement_temperature",T)
2357            defsigL = 3*[-0.00001] + 3*[-0.001] + [-0.01] # significance to use when no sigma
2358            prevsig = 0
2359            for lbl,defsig,val,sig in zip(cellNames,defsigL,cellList,cellSig):
2360                if sig:
2361                    txt = G2mth.ValEsd(val,sig)
2362                    prevsig = -sig # use this as the significance for next value
2363                else:
2364                    txt = G2mth.ValEsd(val,min(defsig,prevsig),True)
2365                WriteCIFitem(self.fp, '_cell_'+lbl,txt)
2366
2367            mass = G2mth.getMass(phasedict['General'])
2368            Volume = cellList[6]
2369            density = mass/(0.6022137*Volume)
2370            WriteCIFitem(self.fp, '_exptl_crystal_density_diffrn',
2371                    G2mth.ValEsd(density,-0.001))
2372
2373            # report atom params
2374            if phasedict['General']['Type'] in ['nuclear','macromolecular']:        #this needs macromolecular variant, etc!
2375                WriteSeqAtomsNuclear(self.fp, cellList, phasedict, phasenam, histname, 
2376                                         self.seqData, self.OverallParms['Rigid bodies'])
2377            else:
2378                print("Warning: no export for sequential "+str(phasedict['General']['Type'])+" coordinates implemented")
2379#                raise Exception("no export for "+str(phasedict['General']['Type'])+" coordinates implemented")
2380
2381            if phasedict['General']['Type'] == 'nuclear':
2382                WriteSeqDistances(phasenam,histname,phasedict,cellList,self.seqData)
2383
2384            # N.B. map info probably not possible w/sequential
2385#            if 'Map' in phasedict['General'] and 'minmax' in phasedict['General']['Map']:
2386#                WriteCIFitem(self.fp, '\n# Difference density results')
2387#                MinMax = phasedict['General']['Map']['minmax']
2388#                WriteCIFitem(self.fp, '_refine_diff_density_max',G2mth.ValEsd(MinMax[0],-0.009))
2389#                WriteCIFitem(self.fp, '_refine_diff_density_min',G2mth.ValEsd(MinMax[1],-0.009))
2390               
2391        def WritePhaseInfo(phasenam,quick=True,oneblock=True):
2392            'Write out the phase information for the selected phase'
2393            WriteCIFitem(self.fp, '\n# phase info for '+str(phasenam) + ' follows')
2394            phasedict = self.Phases[phasenam] # pointer to current phase info
2395            WriteCIFitem(self.fp, '_pd_phase_name', phasenam)
2396            cellList,cellSig = self.GetCell(phasenam)
2397            if quick:  # leave temperature as unknown
2398                WriteCIFitem(self.fp,"_cell_measurement_temperature","?")
2399            elif oneblock:
2400                pass # temperature should be written when the histogram saved later
2401            else: # get T set in _SelectPhaseT_CellSelectHist and possibly get new cell params
2402                T,hRanId = self.CellHistSelection.get(phasedict['ranId'],
2403                                                          ('?',None))
2404                try:
2405                    T = G2mth.ValEsd(T,-1.0)
2406                except:
2407                    pass
2408                WriteCIFitem(self.fp,"_cell_measurement_temperature",T)
2409                for h in self.Histograms:
2410                    if self.Histograms[h]['ranId'] == hRanId:
2411                        pId = phasedict['pId']
2412                        hId = self.Histograms[h]['hId']
2413                        cellList,cellSig = G2stIO.getCellSU(pId,hId,
2414                                        phasedict['General']['SGData'],
2415                                        self.parmDict,
2416                                        self.OverallParms['Covariance'])
2417                        break
2418                else:
2419                    T = '?'
2420
2421            defsigL = 3*[-0.00001] + 3*[-0.001] + [-0.01] # significance to use when no sigma
2422            prevsig = 0
2423            for lbl,defsig,val,sig in zip(cellNames,defsigL,cellList,cellSig):
2424                if sig:
2425                    txt = G2mth.ValEsd(val,sig)
2426                    prevsig = -sig # use this as the significance for next value
2427                else:
2428                    txt = G2mth.ValEsd(val,min(defsig,prevsig),True)
2429                WriteCIFitem(self.fp, '_cell_'+lbl,txt)
2430               
2431            density = G2mth.getDensity(phasedict['General'])[0]
2432            WriteCIFitem(self.fp, '_exptl_crystal_density_diffrn',
2433                    G2mth.ValEsd(density,-0.001))                   
2434
2435            WriteCIFitem(self.fp, '_symmetry_cell_setting',
2436                         phasedict['General']['SGData']['SGSys'])
2437
2438            if phasedict['General']['Type'] in ['nuclear','macromolecular']:
2439                spacegroup = phasedict['General']['SGData']['SpGrp'].strip()
2440                # regularize capitalization and remove trailing H/R
2441                spacegroup = spacegroup[0].upper() + spacegroup[1:].lower().rstrip('rh ')
2442                WriteCIFitem(self.fp, '_symmetry_space_group_name_H-M',spacegroup)
2443   
2444                # generate symmetry operations including centering and center of symmetry
2445                SymOpList,offsetList,symOpList,G2oprList,G2opcodes = G2spc.AllOps(
2446                    phasedict['General']['SGData'])
2447                WriteCIFitem(self.fp, 'loop_\n    _space_group_symop_id\n    _space_group_symop_operation_xyz')
2448                for i,op in enumerate(SymOpList,start=1):
2449                    WriteCIFitem(self.fp, '   {:3d}  {:}'.format(i,op.lower()))
2450            elif phasedict['General']['Type'] == 'magnetic':
2451                parentSpGrp = phasedict['General']['SGData']['SpGrp'].strip()
2452                parentSpGrp = parentSpGrp[0].upper() + parentSpGrp[1:].lower().rstrip('rh ')
2453                WriteCIFitem(self.fp, '_parent_space_group.name_H-M_alt',parentSpGrp)
2454#                [Trans,Uvec,Vvec] = phasedict['General']['SGData']['fromParent']         #save these
2455                spacegroup = phasedict['General']['SGData']['MagSpGrp'].strip()
2456                spacegroup = spacegroup[0].upper() + spacegroup[1:].lower().rstrip('rh ')
2457                WriteCIFitem(self.fp, '_space_group_magn.name_BNS',spacegroup)
2458                WriteCIFitem(self.fp, '_space_group.magn_point_group',phasedict['General']['SGData']['MagPtGp'])
2459
2460                # generate symmetry operations including centering and center of symmetry
2461                SymOpList,offsetList,symOpList,G2oprList,G2opcodes = G2spc.AllOps(
2462                    phasedict['General']['SGData'])
2463                SpnFlp = phasedict['General']['SGData']['SpnFlp']
2464                WriteCIFitem(self.fp, 'loop_\n    _space_group_symop_magn_operation.id\n    _space_group_symop_magn_operation.xyz')
2465                for i,op in enumerate(SymOpList,start=1):
2466                    if SpnFlp[i-1] >0:
2467                        opr = op.lower()+',+1'
2468                    else:
2469                        opr = op.lower()+',-1'
2470                    WriteCIFitem(self.fp, '   {:3d}  {:}'.format(i,opr))
2471
2472            # loop over histogram(s) used in this phase
2473            if not oneblock and not self.quickmode:
2474                # report pointers to the histograms used in this phase
2475                histlist = []
2476                for hist in self.Phases[phasenam]['Histograms']:
2477                    if self.Phases[phasenam]['Histograms'][hist]['Use']:
2478                        if phasebyhistDict.get(hist):
2479                            phasebyhistDict[hist].append(phasenam)
2480                        else:
2481                            phasebyhistDict[hist] = [phasenam,]
2482                        blockid = datablockidDict.get(hist)
2483                        if not blockid:
2484                            print("Internal error: no block for data. Phase "+str(
2485                                phasenam)+" histogram "+str(hist))
2486                            histlist = []
2487                            break
2488                        histlist.append(blockid)
2489
2490                if len(histlist) == 0:
2491                    WriteCIFitem(self.fp, '# Note: phase has no associated data')
2492
2493            # report atom params
2494            if phasedict['General']['Type'] in ['nuclear','macromolecular']:        #this needs macromolecular variant, etc!
2495                try:
2496                    self.labellist
2497                except AttributeError:
2498                    self.labellist = []
2499                WriteAtomsNuclear(self.fp, self.Phases[phasenam], phasenam,
2500                                  self.parmDict, self.sigDict, self.labellist,
2501                                      self.OverallParms['Rigid bodies'])
2502            else:
2503                try:
2504                    self.labellist
2505                except AttributeError:
2506                    self.labellist = []
2507                WriteAtomsMagnetic(self.fp, self.Phases[phasenam], phasenam,
2508                                  self.parmDict, self.sigDict, self.labellist)
2509#                raise Exception("no export for "+str(phasedict['General']['Type'])+" coordinates implemented")
2510            keV = None             
2511            if oneblock: # get xray wavelength
2512                lamlist = []
2513                for hist in self.Histograms:
2514                    if 'X' not in self.Histograms[hist]['Instrument Parameters'][0]['Type'][0]:
2515                        continue
2516                    for k in ('Lam','Lam1'):
2517                        if k in self.Histograms[hist]['Instrument Parameters'][0]:
2518                            lamlist.append(self.Histograms[hist]['Instrument Parameters'][0][k][0])
2519                            break
2520                if len(lamlist) == 1:
2521                    keV = 12.397639/lamlist[0]
2522
2523            # report cell contents                       
2524            WriteComposition(self.fp, self.Phases[phasenam], phasenam, self.parmDict, self.quickmode, keV)
2525            if not self.quickmode and phasedict['General']['Type'] == 'nuclear':      # report distances and angles
2526                WriteDistances(phasenam)
2527            if 'Map' in phasedict['General'] and 'minmax' in phasedict['General']['Map']:
2528                WriteCIFitem(self.fp, '\n# Difference density results')
2529                MinMax = phasedict['General']['Map']['minmax']
2530                WriteCIFitem(self.fp, '_refine_diff_density_max',G2mth.ValEsd(MinMax[0],-0.009))
2531                WriteCIFitem(self.fp, '_refine_diff_density_min',G2mth.ValEsd(MinMax[1],-0.009))
2532
2533        def WritePhaseInfoMM(phasenam,quick=True,oneblock=True):
2534            'Write out the phase information for the selected phase for a macromolecular phase'
2535            WriteCIFitem(self.fp, '\n# phase info for '+str(phasenam) + ' follows')
2536            phasedict = self.Phases[phasenam] # pointer to current phase info
2537            WriteCIFitem(self.fp, '_pd_phase_name', phasenam)
2538            cellList,cellSig = self.GetCell(phasenam)
2539            if oneblock:
2540                pass # temperature should be written when the histogram saved later
2541            else: # get T set in _SelectPhaseT_CellSelectHist and possibly get new cell params
2542                T,hRanId = self.CellHistSelection.get(phasedict['ranId'],
2543                                                          ('?',None))
2544                try:
2545                    T = G2mth.ValEsd(T,-1.0)
2546                except:
2547                    pass
2548                WriteCIFitem(self.fp,"_cell_measurement.temp",T)
2549                for h in self.Histograms:
2550                    if self.Histograms[h]['ranId'] == hRanId:
2551                        pId = phasedict['pId']
2552                        hId = self.Histograms[h]['hId']
2553                        cellList,cellSig = G2stIO.getCellSU(pId,hId,
2554                                        phasedict['General']['SGData'],
2555                                        self.parmDict,
2556                                        self.OverallParms['Covariance'])
2557                        break
2558                else:
2559                    T = '?'
2560
2561            defsigL = 3*[-0.00001] + 3*[-0.001] + [-0.01] # significance to use when no sigma
2562            prevsig = 0
2563            for lbl,defsig,val,sig in zip(cellNames,defsigL,cellList,cellSig):
2564                if sig:
2565                    txt = G2mth.ValEsd(val,sig)
2566                    prevsig = -sig # use this as the significance for next value
2567                else:
2568                    txt = G2mth.ValEsd(val,min(defsig,prevsig),True)
2569                WriteCIFitem(self.fp, '_cell.'+lbl,txt)
2570               
2571            density = G2mth.getDensity(phasedict['General'])[0]
2572            WriteCIFitem(self.fp, '_exptl_crystal.density_diffrn',
2573                    G2mth.ValEsd(density,-0.001))                   
2574
2575            WriteCIFitem(self.fp, '_symmetry.cell_setting',
2576                         phasedict['General']['SGData']['SGSys'])
2577
2578            spacegroup = phasedict['General']['SGData']['SpGrp'].strip()
2579            # regularize capitalization and remove trailing H/R
2580            spacegroup = spacegroup[0].upper() + spacegroup[1:].lower().rstrip('rh ')
2581            WriteCIFitem(self.fp, '_symmetry.space_group_name_H-M',spacegroup)
2582
2583            # generate symmetry operations including centering and center of symmetry
2584            SymOpList,offsetList,symOpList,G2oprList,G2opcodes = G2spc.AllOps(
2585                phasedict['General']['SGData'])
2586            WriteCIFitem(self.fp, 'loop_\n    _space_group.symop_id\n    _space_group.symop_operation_xyz')
2587            for i,op in enumerate(SymOpList,start=1):
2588                WriteCIFitem(self.fp, '   {:3d}  {:}'.format(i,op.lower()))
2589
2590            # loop over histogram(s) used in this phase
2591            if not oneblock and not self.quickmode:
2592                # report pointers to the histograms used in this phase
2593                histlist = []
2594                for hist in self.Phases[phasenam]['Histograms']:
2595                    if self.Phases[phasenam]['Histograms'][hist]['Use']:
2596                        if phasebyhistDict.get(hist):
2597                            phasebyhistDict[hist].append(phasenam)
2598                        else:
2599                            phasebyhistDict[hist] = [phasenam,]
2600                        blockid = datablockidDict.get(hist)
2601                        if not blockid:
2602                            print("Internal error: no block for data. Phase "+str(
2603                                phasenam)+" histogram "+str(hist))
2604                            histlist = []
2605                            break
2606                        histlist.append(blockid)
2607
2608                if len(histlist) == 0:
2609                    WriteCIFitem(self.fp, '# Note: phase has no associated data')
2610
2611            # report atom params
2612            try:
2613                self.labellist
2614            except AttributeError:
2615                self.labellist = []
2616            WriteAtomsMM(self.fp, self.Phases[phasenam], phasenam,
2617                              self.parmDict, self.sigDict, 
2618                                  self.OverallParms['Rigid bodies'])
2619
2620            keV = None             
2621            if oneblock: # get xray wavelength
2622                lamlist = []
2623                for hist in self.Histograms:
2624                    if 'X' not in self.Histograms[hist]['Instrument Parameters'][0]['Type'][0]:
2625                        continue
2626                    for k in ('Lam','Lam1'):
2627                        if k in self.Histograms[hist]['Instrument Parameters'][0]:
2628                            lamlist.append(self.Histograms[hist]['Instrument Parameters'][0][k][0])
2629                            break
2630                if len(lamlist) == 1:
2631                    keV = 12.397639/lamlist[0]
2632
2633            # report cell contents                       
2634            WriteCompositionMM(self.fp, self.Phases[phasenam], phasenam, self.parmDict, self.quickmode, keV)
2635            if not self.quickmode and phasedict['General']['Type'] == 'nuclear':      # report distances and angles
2636                WriteDistances(phasenam)
2637            if 'Map' in phasedict['General'] and 'minmax' in phasedict['General']['Map']:
2638                WriteCIFitem(self.fp, '\n# Difference density results')
2639                MinMax = phasedict['General']['Map']['minmax']
2640                WriteCIFitem(self.fp, '_refine_diff_density_max',G2mth.ValEsd(MinMax[0],-0.009))
2641                WriteCIFitem(self.fp, '_refine_diff_density_min',G2mth.ValEsd(MinMax[1],-0.009))
2642               
2643        def Yfmt(ndec,val):
2644            'Format intensity values'
2645            try:
2646                out = ("{:."+str(ndec)+"f}").format(val)
2647                out = out.rstrip('0')  # strip zeros to right of decimal
2648                return out.rstrip('.')  # and decimal place when not needed
2649            except TypeError:
2650                print(val)
2651                return '.'
2652           
2653        def WriteReflStat(refcount,hklmin,hklmax,dmin,dmax,nRefSets=1):
2654            'Write reflection statistics'
2655            WriteCIFitem(self.fp, '_reflns_number_total', str(refcount))
2656            if hklmin is not None and nRefSets == 1: # hkl range has no meaning with multiple phases
2657                WriteCIFitem(self.fp, '_reflns_limit_h_min', str(int(hklmin[0])))
2658                WriteCIFitem(self.fp, '_reflns_limit_h_max', str(int(hklmax[0])))
2659                WriteCIFitem(self.fp, '_reflns_limit_k_min', str(int(hklmin[1])))
2660                WriteCIFitem(self.fp, '_reflns_limit_k_max', str(int(hklmax[1])))
2661                WriteCIFitem(self.fp, '_reflns_limit_l_min', str(int(hklmin[2])))
2662                WriteCIFitem(self.fp, '_reflns_limit_l_max', str(int(hklmax[2])))
2663            if hklmin is not None:
2664                WriteCIFitem(self.fp, '_reflns_d_resolution_low  ', G2mth.ValEsd(dmax,-0.009))
2665                WriteCIFitem(self.fp, '_reflns_d_resolution_high ', G2mth.ValEsd(dmin,-0.009))
2666
2667        def WritePowderData(histlbl,seq=False):
2668            'Write out the selected powder diffraction histogram info'
2669            histblk = self.Histograms[histlbl]
2670            inst = histblk['Instrument Parameters'][0]
2671            hId = histblk['hId']
2672            pfx = ':' + str(hId) + ':'
2673
2674            if 'Lam1' in inst:
2675                ratio = self.parmDict.get('I(L2)/I(L1)',inst['I(L2)/I(L1)'][1])
2676                sratio = self.sigDict.get('I(L2)/I(L1)',-0.0009)
2677                lam1 = self.parmDict.get('Lam1',inst['Lam1'][1])
2678                slam1 = self.sigDict.get('Lam1',-0.00009)
2679                lam2 = self.parmDict.get('Lam2',inst['Lam2'][1])
2680                slam2 = self.sigDict.get('Lam2',-0.00009)
2681                # always assume Ka1 & Ka2 if two wavelengths are present
2682                WriteCIFitem(self.fp, '_diffrn_radiation_type','K\\a~1,2~')
2683                WriteCIFitem(self.fp, 'loop_' +
2684                             '\n   _diffrn_radiation_wavelength' +
2685                             '\n   _diffrn_radiation_wavelength_wt' +
2686                             '\n   _diffrn_radiation_wavelength_id')
2687                WriteCIFitem(self.fp, '  ' + PutInCol(G2mth.ValEsd(lam1,slam1),15)+
2688                             PutInCol('1.0',15) +
2689                             PutInCol('1',5))
2690                WriteCIFitem(self.fp, '  ' + PutInCol(G2mth.ValEsd(lam2,slam2),15)+
2691                             PutInCol(G2mth.ValEsd(ratio,sratio),15)+
2692                             PutInCol('2',5))
2693            elif 'Lam' in inst:
2694                lam1 = self.parmDict.get('Lam',inst['Lam'][1])
2695                slam1 = self.sigDict.get('Lam',-0.00009)
2696                WriteCIFitem(self.fp, '_diffrn_radiation_wavelength',G2mth.ValEsd(lam1,slam1))
2697
2698            if not oneblock:
2699                if seq:
2700                    pass
2701                elif not phasebyhistDict.get(histlbl):
2702                    WriteCIFitem(self.fp, '\n# No phases associated with this data set')
2703                else:
2704                    WriteCIFitem(self.fp, '\n# PHASE TABLE')
2705                    WriteCIFitem(self.fp, 'loop_' +
2706                                 '\n   _pd_phase_id' +
2707                                 '\n   _pd_phase_block_id' +
2708                                 '\n   _pd_phase_mass_%')
2709                    wtFrSum = 0.
2710                    for phasenam in phasebyhistDict.get(histlbl):
2711                        hapData = self.Phases[phasenam]['Histograms'][histlbl]
2712                        General = self.Phases[phasenam]['General']
2713                        wtFrSum += hapData['Scale'][0]*General['Mass']
2714
2715                    for phasenam in phasebyhistDict.get(histlbl):
2716                        hapData = self.Phases[phasenam]['Histograms'][histlbl]
2717                        General = self.Phases[phasenam]['General']
2718                        wtFr = hapData['Scale'][0]*General['Mass']/wtFrSum
2719                        pfx = str(self.Phases[phasenam]['pId'])+':'+str(hId)+':'
2720                        if pfx+'Scale' in self.sigDict:
2721                            sig = self.sigDict[pfx+'Scale']*wtFr/hapData['Scale'][0]
2722                        else:
2723                            sig = -0.0001
2724                        WriteCIFitem(self.fp,
2725                            '  '+
2726                            str(self.Phases[phasenam]['pId']) +
2727                            '  '+datablockidDict[phasenam]+
2728                            '  '+G2mth.ValEsd(wtFr,sig)
2729                            )
2730                    WriteCIFitem(self.fp, 'loop_' +
2731                                 '\n   _gsas_proc_phase_R_F_factor' +
2732                                 '\n   _gsas_proc_phase_R_Fsqd_factor' +
2733                                 '\n   _gsas_proc_phase_id' +
2734                                 '\n   _gsas_proc_phase_block_id')
2735                    for phasenam in phasebyhistDict.get(histlbl):
2736                        pfx = str(self.Phases[phasenam]['pId'])+':'+str(hId)+':'
2737                        WriteCIFitem(self.fp,
2738                            '  '+
2739                            '  '+G2mth.ValEsd(histblk[pfx+'Rf']/100.,-.00009) +
2740                            '  '+G2mth.ValEsd(histblk[pfx+'Rf^2']/100.,-.00009)+
2741                            '  '+str(self.Phases[phasenam]['pId'])+
2742                            '  '+datablockidDict[phasenam]
2743                            )
2744            elif len(self.Phases) == 1:
2745                # single phase in this histogram
2746                # get the phase number here
2747                pId = self.Phases[list(self.Phases.keys())[0]]['pId']
2748                pfx = str(pId)+':'+str(hId)+':'
2749                WriteCIFitem(self.fp, '_refine_ls_R_F_factor      ','%.5f'%(histblk[pfx+'Rf']/100.))
2750                WriteCIFitem(self.fp, '_refine_ls_R_Fsqd_factor   ','%.5f'%(histblk[pfx+'Rf^2']/100.))
2751               
2752            try:
2753                WriteCIFitem(self.fp, '_pd_proc_ls_prof_R_factor   ','%.5f'%(histblk['R']/100.))
2754                WriteCIFitem(self.fp, '_pd_proc_ls_prof_wR_factor  ','%.5f'%(histblk['wR']/100.))
2755                WriteCIFitem(self.fp, '_gsas_proc_ls_prof_R_B_factor ','%.5f'%(histblk['Rb']/100.))
2756                WriteCIFitem(self.fp, '_gsas_proc_ls_prof_wR_B_factor','%.5f'%(histblk['wRb']/100.))
2757                WriteCIFitem(self.fp, '_pd_proc_ls_prof_wR_expected','%.5f'%(histblk['wRmin']/100.))
2758            except KeyError:
2759                pass
2760
2761            if histblk['Instrument Parameters'][0]['Type'][1][1] == 'X':
2762                WriteCIFitem(self.fp, '_diffrn_radiation_probe','x-ray')
2763                pola = histblk['Instrument Parameters'][0].get('Polariz.')
2764                if pola:
2765                    pfx = ':' + str(hId) + ':'
2766                    sig = self.sigDict.get(pfx+'Polariz.',-0.0009)
2767                    txt = G2mth.ValEsd(pola[1],sig)
2768                    WriteCIFitem(self.fp, '_diffrn_radiation_polarisn_ratio',txt)
2769            elif histblk['Instrument Parameters'][0]['Type'][1][1] == 'N':
2770                WriteCIFitem(self.fp, '_diffrn_radiation_probe','neutron')
2771            if 'T' in inst['Type'][0]:
2772                txt = G2mth.ValEsd(inst['2-theta'][0],-0.009)
2773                WriteCIFitem(self.fp, '_pd_meas_2theta_fixed',txt)
2774
2775            WriteCIFitem(self.fp, '_pd_proc_ls_background_function',FormatBackground(histblk['Background'],histblk['hId']))
2776
2777            # TODO: this will need help from Bob
2778            #WriteCIFitem(self.fp, '_exptl_absorpt_process_details','?')
2779            #WriteCIFitem(self.fp, '_exptl_absorpt_correction_T_min','?')
2780            #WriteCIFitem(self.fp, '_exptl_absorpt_correction_T_max','?')
2781            #C extinction
2782            #WRITE(IUCIF,'(A)') '# Extinction correction'
2783            #CALL WRVAL(IUCIF,'_gsas_exptl_extinct_corr_T_min',TEXT(1:10))
2784            #CALL WRVAL(IUCIF,'_gsas_exptl_extinct_corr_T_max',TEXT(11:20))
2785
2786            # code removed because it is causing duplication in histogram block 1/26/19 BHT
2787            #if not oneblock:                 # instrumental profile terms go here
2788            #    WriteCIFitem(self.fp, '_pd_proc_ls_profile_function',
2789            #        FormatInstProfile(histblk["Instrument Parameters"],histblk['hId']))
2790
2791            #refprx = '_refln.' # mm
2792            refprx = '_refln_' # normal
2793            # data collection parameters for the powder dataset
2794
2795            temperature = histblk['Sample Parameters'].get('Temperature') # G2 uses K
2796            if not temperature:
2797                T = '?'
2798            else:
2799                T = G2mth.ValEsd(temperature,-0.009,True) # CIF uses K
2800            WriteCIFitem(self.fp, '_diffrn_ambient_temperature',T)
2801
2802            pressure = histblk['Sample Parameters'].get('Pressure') #G2 uses mega-Pascal
2803            if not pressure:
2804                P = '?'
2805            else:
2806                P = G2mth.ValEsd(pressure*1000,-0.09,True) # CIF uses kilopascal (G2 Mpa)
2807            WriteCIFitem(self.fp, '_diffrn_ambient_pressure',P)
2808
2809            WriteCIFitem(self.fp, '\n# STRUCTURE FACTOR TABLE')
2810            # compute maximum intensity reflection
2811            Imax = 0
2812            phaselist = []
2813            for phasenam in histblk['Reflection Lists']:
2814                try:
2815                    scale = self.Phases[phasenam]['Histograms'][histlbl]['Scale'][0]
2816                except KeyError: # reflection table from removed phase?
2817                    continue
2818                phaselist.append(phasenam)
2819                refList = np.asarray(histblk['Reflection Lists'][phasenam]['RefList'])
2820                I100 = scale*refList.T[8]*refList.T[11]
2821                #Icorr = np.array([refl[13] for refl in histblk['Reflection Lists'][phasenam]])[0]
2822                #FO2 = np.array([refl[8] for refl in histblk['Reflection Lists'][phasenam]])
2823                #I100 = scale*FO2*Icorr
2824                Imax = max(Imax,max(I100))
2825
2826            WriteCIFitem(self.fp, 'loop_')
2827            if len(phaselist) > 1:
2828                WriteCIFitem(self.fp, '   _pd_refln_phase_id')
2829            WriteCIFitem(self.fp, '   ' + refprx + 'index_h' +
2830                         '\n   ' + refprx + 'index_k' +
2831                         '\n   ' + refprx + 'index_l' +
2832                         '\n   ' + refprx + 'F_squared_meas' +
2833                         '\n   ' + refprx + 'F_squared_calc' +
2834                         '\n   ' + refprx + 'phase_calc' +
2835                         '\n   _refln_d_spacing')
2836            if Imax > 0:
2837                WriteCIFitem(self.fp, '   _gsas_i100_meas')
2838
2839            refcount = 0
2840            hklmin = None
2841            hklmax = None
2842            dmax = None
2843            dmin = None
2844            for phasenam in phaselist:
2845                scale = self.Phases[phasenam]['Histograms'][histlbl]['Scale'][0]
2846                phaseid = self.Phases[phasenam]['pId']
2847                refcount += len(histblk['Reflection Lists'][phasenam]['RefList'])
2848                refList = np.asarray(histblk['Reflection Lists'][phasenam]['RefList'])
2849                I100 = scale*refList.T[8]*refList.T[11]
2850                for j,ref in enumerate(histblk['Reflection Lists'][phasenam]['RefList']):
2851                    if DEBUG:
2852                        print('DEBUG: skipping reflection list')
2853                        break
2854                    if hklmin is None:
2855                        hklmin = copy.copy(ref[0:3])
2856                        hklmax = copy.copy(ref[0:3])
2857                    if dmin is None:
2858                         dmax = dmin = ref[4]
2859                    if len(phaselist) > 1:
2860                        s = PutInCol(phaseid,2)
2861                    else:
2862                        s = ""
2863                    for i,hkl in enumerate(ref[0:3]):
2864                        hklmax[i] = max(hkl,hklmax[i])
2865                        hklmin[i] = min(hkl,hklmin[i])
2866                        s += PutInCol(int(hkl),4)
2867                    for I in ref[8:10]:
2868                        s += PutInCol(G2mth.ValEsd(I,-0.0009),10)
2869                    s += PutInCol(G2mth.ValEsd(ref[10],-0.9),7)
2870                    dmax = max(dmax,ref[4])
2871                    dmin = min(dmin,ref[4])
2872                    s += PutInCol(G2mth.ValEsd(ref[4],-0.00009),8)
2873                    if Imax > 0:
2874                        s += PutInCol(G2mth.ValEsd(100.*I100[j]/Imax,-0.09),6)
2875                    WriteCIFitem(self.fp, "  "+s)
2876
2877            WriteReflStat(refcount,hklmin,hklmax,dmin,dmax,len(phaselist))
2878            WriteCIFitem(self.fp, '\n# POWDER DATA TABLE')
2879            # is data fixed step? If the step varies by <0.01% treat as fixed step
2880            steps = abs(histblk['Data'][0][1:] - histblk['Data'][0][:-1])
2881            if (max(steps)-min(steps)) > np.mean(steps)/10000.:
2882                fixedstep = False
2883            else:
2884                fixedstep = True
2885
2886            zero = None
2887            if fixedstep and 'T' not in inst['Type'][0]: # and not TOF
2888                WriteCIFitem(self.fp, '_pd_meas_2theta_range_min', G2mth.ValEsd(histblk['Data'][0][0],-0.00009))
2889                WriteCIFitem(self.fp, '_pd_meas_2theta_range_max', G2mth.ValEsd(histblk['Data'][0][-1],-0.00009))
2890                WriteCIFitem(self.fp, '_pd_meas_2theta_range_inc', G2mth.ValEsd(np.mean(steps),-0.00009))
2891                # zero correct, if defined
2892                zerolst = histblk['Instrument Parameters'][0].get('Zero')
2893                if zerolst: zero = zerolst[1]
2894                zero = self.parmDict.get('Zero',zero)
2895                if zero:
2896                    WriteCIFitem(self.fp, '_pd_proc_2theta_range_min', G2mth.ValEsd(histblk['Data'][0][0]-zero,-0.00009))
2897                    WriteCIFitem(self.fp, '_pd_proc_2theta_range_max', G2mth.ValEsd(histblk['Data'][0][-1]-zero,-0.00009))
2898                    WriteCIFitem(self.fp, '_pd_proc_2theta_range_inc', G2mth.ValEsd(steps.sum()/len(steps),-0.00009))
2899
2900            if zero:
2901                WriteCIFitem(self.fp, '_pd_proc_number_of_points', str(len(histblk['Data'][0])))
2902            else:
2903                WriteCIFitem(self.fp, '_pd_meas_number_of_points', str(len(histblk['Data'][0])))
2904            WriteCIFitem(self.fp, '\nloop_')
2905            #            WriteCIFitem(self.fp, '   _pd_proc_d_spacing') # need easy way to get this
2906            if not fixedstep:
2907                if zero:
2908                    WriteCIFitem(self.fp, '   _pd_proc_2theta_corrected')
2909                elif 'T' in inst['Type'][0]: # and not TOF
2910                    WriteCIFitem(self.fp, '   _pd_meas_time_of_flight')
2911                else:
2912                    WriteCIFitem(self.fp, '   _pd_meas_2theta_scan')
2913            # at least for now, always report weights.
2914            #if countsdata:
2915            #    WriteCIFitem(self.fp, '   _pd_meas_counts_total')
2916            #else:
2917            WriteCIFitem(self.fp, '   _pd_meas_intensity_total')
2918            WriteCIFitem(self.fp, '   _pd_calc_intensity_total')
2919            WriteCIFitem(self.fp, '   _pd_proc_intensity_bkg_calc')
2920            WriteCIFitem(self.fp, '   _pd_proc_ls_weight')
2921            maxY = max(histblk['Data'][1].max(),histblk['Data'][3].max())
2922            if maxY < 0: maxY *= -10 # this should never happen, but...
2923            ndec = max(0,10-int(np.log10(maxY))-1) # 10 sig figs should be enough
2924            maxSU = histblk['Data'][2].max()
2925            if maxSU < 0: maxSU *= -1 # this should never happen, but...
2926            ndecSU = max(0,8-int(np.log10(maxSU))-1) # 8 sig figs should be enough
2927            lowlim,highlim = histblk['Limits'][1]
2928
2929            if DEBUG:
2930                print('DEBUG: skipping profile list')
2931            else:
2932                for x,yobs,yw,ycalc,ybkg in zip(histblk['Data'][0].data,        #get the data from these masked arrays
2933                                                histblk['Data'][1].data,
2934                                                histblk['Data'][2].data,
2935                                                histblk['Data'][3].data,
2936                                                histblk['Data'][4].data):
2937                    if lowlim <= x <= highlim:
2938                        pass
2939                    else:
2940                        yw = 0.0 # show the point is not in use
2941
2942                    if fixedstep:
2943                        s = ""
2944                    elif zero:
2945                        s = PutInCol(G2mth.ValEsd(x-zero,-0.00009),10)
2946                    else:
2947                        s = PutInCol(G2mth.ValEsd(x,-0.00009),10)
2948                    s += PutInCol(Yfmt(ndec,yobs),12)
2949                    s += PutInCol(Yfmt(ndec,ycalc),12)
2950                    s += PutInCol(Yfmt(ndec,ybkg),11)
2951                    s += PutInCol(Yfmt(ndecSU,yw),9)
2952                    WriteCIFitem(self.fp, "  "+s)
2953
2954        def WriteSingleXtalData(histlbl):
2955            'Write out the selected single crystal histogram info'
2956            histblk = self.Histograms[histlbl]
2957
2958            #refprx = '_refln.' # mm
2959            refprx = '_refln_' # normal
2960
2961            WriteCIFitem(self.fp, '\n# STRUCTURE FACTOR TABLE')
2962            WriteCIFitem(self.fp, 'loop_' +
2963                         '\n   ' + refprx + 'index_h' +
2964                         '\n   ' + refprx + 'index_k' +
2965                         '\n   ' + refprx + 'index_l' +
2966                         '\n   ' + refprx + 'F_squared_meas' +
2967                         '\n   ' + refprx + 'F_squared_sigma' +
2968                         '\n   ' + refprx + 'F_squared_calc' +
2969                         '\n   ' + refprx + 'phase_calc'
2970                         )
2971
2972            hklmin = None
2973            hklmax = None
2974            dmax = None
2975            dmin = None
2976            refcount = len(histblk['Data']['RefList'])
2977            for ref in histblk['Data']['RefList']:
2978                if ref[3] <= 0:      #skip user rejected reflections (mul <= 0)
2979                    continue
2980                s = "  "
2981                if hklmin is None:
2982                    hklmin = copy.copy(ref[0:3])
2983                    hklmax = copy.copy(ref[0:3])
2984                    dmax = dmin = ref[4]
2985                for i,hkl in enumerate(ref[0:3]):
2986                    hklmax[i] = max(hkl,hklmax[i])
2987                    hklmin[i] = min(hkl,hklmin[i])
2988                    s += PutInCol(int(hkl),4)
2989                if ref[5] == 0.0:
2990                    s += PutInCol(G2mth.ValEsd(ref[8],0),12)
2991                    s += PutInCol('.',10)
2992                    s += PutInCol(G2mth.ValEsd(ref[9],0),12)
2993                else:
2994                    sig = ref[6] * ref[8] / ref[5]
2995                    s += PutInCol(G2mth.ValEsd(ref[8],-abs(sig/10)),12)
2996                    s += PutInCol(G2mth.ValEsd(sig,-abs(sig)/10.),10)
2997                    s += PutInCol(G2mth.ValEsd(ref[9],-abs(sig/10)),12)
2998                s += PutInCol(G2mth.ValEsd(ref[10],-0.9),7)
2999                dmax = max(dmax,ref[4])
3000                dmin = min(dmin,ref[4])
3001                WriteCIFitem(self.fp, s)
3002            if not self.quickmode: # statistics only in a full CIF
3003                WriteReflStat(refcount,hklmin,hklmax,dmin,dmax)
3004                hId = histblk['hId']
3005                hfx = '0:'+str(hId)+':'
3006                phfx = '%d:%d:'%(0,hId)
3007                extType,extModel,extParms = self.Phases[phasenam]['Histograms'][histlbl]['Extinction']
3008                if extModel != 'None':
3009                    WriteCIFitem(self.fp, '# Extinction scaled by 1.e5')
3010                    WriteCIFitem(self.fp, '_refine_ls_extinction_method','Becker-Coppens %s %s'%(extModel,extType))
3011                    sig = -1.e-3
3012                    if extModel == 'Primary':
3013                        parm = extParms['Ep'][0]*1.e5
3014                        if extParms['Ep'][1]:
3015                            sig = self.sigDict[phfx+'Ep']*1.e5
3016                        text = G2mth.ValEsd(parm,sig)
3017                    elif extModel == 'Secondary Type I':
3018                        parm = extParms['Eg'][0]*1.e5
3019                        if extParms['Eg'][1]:
3020                            sig = self.sigDict[phfx+'Eg']*1.e5
3021                        text = G2mth.ValEsd(parm,sig)
3022                    elif extModel == 'Secondary Type II':
3023                        parm = extParms['Es'][0]*1.e5
3024                        if extParms['Es'][1]:
3025                            sig = self.sigDict[phfx+'Es']*1.e5
3026                        text = G2mth.ValEsd(parm,sig)
3027                    elif extModel == 'Secondary Type I & II':
3028                        parm = extParms['Eg'][0]*1.e5
3029                        if extParms['Es'][1]:
3030                            sig = self.sigDict[phfx+'Es']*1.e5
3031                        text = G2mth.ValEsd(parm,sig)
3032                        sig = -1.0e-3
3033                        parm = extParms['Es'][0]*1.e5
3034                        if extParms['Es'][1]:
3035                            sig = self.sigDict[phfx+'Es']*1.e5
3036                        text += G2mth.ValEsd(parm,sig)
3037                    WriteCIFitem(self.fp, '_refine_ls_extinction_coef',text)
3038                    WriteCIFitem(self.fp, '_refine_ls_extinction_expression','Becker & Coppens (1974). Acta Cryst. A30, 129-147')
3039
3040                WriteCIFitem(self.fp, '_refine_ls_wR_factor_gt    ','%.4f'%(histblk['wR']/100.))
3041                WriteCIFitem(self.fp, '_refine_ls_R_factor_gt     ','%.4f'%(histblk[hfx+'Rf']/100.))
3042                WriteCIFitem(self.fp, '_refine_ls_R_Fsqd_factor   ','%.4f'%(histblk[hfx+'Rf^2']/100.))
3043        def EditAuthor(event=None):
3044            'dialog to edit the CIF author info'
3045            'Edit the CIF author name'
3046            dlg = G2G.SingleStringDialog(self.G2frame,
3047                                          'Get CIF Author',
3048                                          'Provide CIF Author name (Last, First)',
3049                                          value=self.author)
3050            if not dlg.Show():
3051                dlg.Destroy()
3052                return False  # cancel was pressed
3053            self.author = dlg.GetValue()
3054            self.shortauthorname = self.author.replace(',','').replace(' ','')[:20]
3055            dlg.Destroy()
3056            try:
3057                self.OverallParms['Controls']["Author"] = self.author # save for future
3058            except KeyError:
3059                pass
3060            return True
3061
3062        def EditInstNames(event=None):
3063            'Provide a dialog for editing instrument names; for sequential fit, only need one name'
3064            dictlist = []
3065            keylist = []
3066            lbllist = []
3067            for hist in sorted(self.Histograms):
3068                if hist.startswith("PWDR"):
3069                    key2 = "Sample Parameters"
3070                    d = self.Histograms[hist][key2]
3071                elif hist.startswith("HKLF"):
3072                    key2 = "Instrument Parameters"
3073                    d = self.Histograms[hist][key2][0]
3074
3075                lbllist.append(hist)
3076                dictlist.append(d)
3077                keylist.append('InstrName')
3078                instrname = d.get('InstrName')
3079                if instrname is None:
3080                    d['InstrName'] = ''
3081                if hist.startswith("PWDR") and seqmode: break               
3082            return G2G.CallScrolledMultiEditor(
3083                self.G2frame,dictlist,keylist,
3084                prelbl=range(1,len(dictlist)+1),
3085                postlbl=lbllist,
3086                title='Instrument names',
3087                header="Edit instrument names. Note that a non-blank\nname is required for all histograms",
3088                CopyButton=True,ASCIIonly=True)
3089
3090        def EditRanges(event):
3091            '''Edit the bond distance/angle search range; phase is determined from
3092            a pointer placed in the button object (.phasedict) that references the
3093            phase dictionary
3094            '''
3095            but = event.GetEventObject()
3096            phasedict = but.phasedict
3097            dlg = G2G.DisAglDialog(
3098                self.G2frame,
3099                phasedict['General']['DisAglCtls'], # edited
3100                phasedict['General'], # defaults
3101                )
3102            if dlg.ShowModal() == wx.ID_OK:
3103                phasedict['General']['DisAglCtls'] = dlg.GetData()
3104            dlg.Destroy()
3105           
3106        def SetCellT(event):
3107            '''Set the temperature value by selection of a histogram
3108            '''
3109            but = event.GetEventObject()
3110            phasenam = but.phase
3111            rId =  self.Phases[phasenam]['ranId']
3112            self.CellHistSelection[rId] = self._CellSelectHist(phasenam)
3113           
3114        def EditCIFDefaults():
3115            '''Fills the CIF Defaults window with controls for editing various CIF export
3116            parameters (mostly related to templates).
3117            '''
3118            if len(self.cifdefs.GetChildren()) > 0:
3119                saveSize = self.cifdefs.GetSize()
3120                self.cifdefs.DestroyChildren()
3121            else:
3122                saveSize = None
3123            self.cifdefs.SetTitle('Edit CIF settings')
3124            vbox = wx.BoxSizer(wx.VERTICAL)
3125            vbox.Add(wx.StaticText(self.cifdefs, wx.ID_ANY,'Creating file '+self.filename))
3126            but = wx.Button(self.cifdefs, wx.ID_ANY,'Edit CIF Author')
3127            but.Bind(wx.EVT_BUTTON,EditAuthor)
3128            vbox.Add(but,0,wx.ALIGN_CENTER,3)
3129            but = wx.Button(self.cifdefs, wx.ID_ANY,'Edit Instrument Name(s)')
3130            but.Bind(wx.EVT_BUTTON,EditInstNames)
3131            vbox.Add(but,0,wx.ALIGN_CENTER,3)
3132            cpnl = wxscroll.ScrolledPanel(self.cifdefs,size=(300,300))
3133            cbox = wx.BoxSizer(wx.VERTICAL)
3134            G2G.HorizontalLine(cbox,cpnl)
3135            cbox.Add(
3136                CIFtemplateSelect(self.cifdefs,
3137                                  cpnl,'publ',self.OverallParms['Controls'],
3138                                  EditCIFDefaults,
3139                                  "Publication (overall) template",
3140                                  ),
3141                0,wx.EXPAND|wx.ALIGN_LEFT|wx.ALL)
3142            for phasenam in sorted(self.Phases.keys()):
3143                G2G.HorizontalLine(cbox,cpnl)
3144                title = 'Phase '+phasenam
3145                phasedict = self.Phases[phasenam] # pointer to current phase info
3146                cbox.Add(
3147                    CIFtemplateSelect(self.cifdefs,
3148                                      cpnl,'phase',phasedict['General'],
3149                                      EditCIFDefaults,
3150                                      title,
3151                                      phasenam),
3152                    0,wx.EXPAND|wx.ALIGN_LEFT|wx.ALL)
3153                cpnl.SetSizer(cbox)
3154                if phasedict['General']['Type'] == 'nuclear':
3155                    but = wx.Button(cpnl, wx.ID_ANY,'Edit distance/angle ranges')
3156                    cbox.Add(but,0,wx.ALIGN_LEFT,0)
3157                    cbox.Add((-1,2))
3158                    but.phasedict = self.Phases[phasenam]  # set a pointer to current phase info
3159                    but.Bind(wx.EVT_BUTTON,EditRanges)     # phase bond/angle ranges
3160                    but = wx.Button(cpnl, wx.ID_ANY,'Set distance/angle publication flags')
3161                    but.phase = phasenam  # set a pointer to current phase info
3162                    but.Bind(wx.EVT_BUTTON,SelectDisAglFlags)     # phase bond/angle ranges
3163                    cbox.Add(but,0,wx.ALIGN_LEFT,0)
3164                if self._CellSelectNeeded(phasenam):
3165                    but = wx.Button(cpnl, wx.ID_ANY,'Select cell temperature')
3166                    cbox.Add(but,0,wx.ALIGN_LEFT,0)
3167                    cbox.Add((-1,2))
3168                    but.phase = phasenam  # set a pointer to current phase info
3169                    but.Bind(wx.EVT_BUTTON,SetCellT)
3170                cbox.Add((-1,2))
3171            for i in sorted(self.powderDict.keys()):
3172                G2G.HorizontalLine(cbox,cpnl)
3173                if seqmode:
3174                    hist = self.powderDict[i]
3175                    histblk = self.Histograms[hist]
3176                    title = 'All Powder datasets'
3177                    self.seqSmplParms = {}
3178                    cbox.Add(
3179                        CIFtemplateSelect(self.cifdefs,
3180                                      cpnl,'powder',self.seqSmplParms,
3181                                      EditCIFDefaults,
3182                                      title,
3183                                      histblk["Sample Parameters"]['InstrName']),
3184                        0,wx.EXPAND|wx.ALIGN_LEFT|wx.ALL)
3185                    break
3186                hist = self.powderDict[i]
3187                histblk = self.Histograms[hist]
3188                title = 'Powder dataset '+hist[5:]
3189                cbox.Add(
3190                    CIFtemplateSelect(self.cifdefs,
3191                                      cpnl,'powder',histblk["Sample Parameters"],
3192                                      EditCIFDefaults,
3193                                      title,
3194                                      histblk["Sample Parameters"]['InstrName']),
3195                    0,wx.EXPAND|wx.ALIGN_LEFT|wx.ALL)
3196            for i in sorted(self.xtalDict.keys()):
3197                G2G.HorizontalLine(cbox,cpnl)
3198                hist = self.xtalDict[i]
3199                histblk = self.Histograms[hist]
3200                title = 'Single Xtal dataset '+hist[5:]
3201                cbox.Add(
3202                    CIFtemplateSelect(self.cifdefs,
3203                                      cpnl,'single',histblk["Instrument Parameters"][0],
3204                                      EditCIFDefaults,
3205                                      title,
3206                                      histblk["Instrument Parameters"][0]['InstrName']),
3207                    0,wx.EXPAND|wx.ALIGN_LEFT|wx.ALL)
3208            cpnl.SetSizer(cbox)
3209            cpnl.SetAutoLayout(1)
3210            cpnl.SetupScrolling()
3211            #cpnl.Bind(rw.EVT_RW_LAYOUT_NEEDED, self.OnLayoutNeeded) # needed if sizes change
3212            cpnl.Layout()
3213
3214            vbox.Add(cpnl, 1, wx.ALIGN_LEFT|wx.ALL|wx.EXPAND, 0)
3215            btnsizer = wx.StdDialogButtonSizer()
3216            btn = wx.Button(self.cifdefs, wx.ID_OK, "Create CIF")
3217            btn.SetDefault()
3218            btnsizer.AddButton(btn)
3219            btn = wx.Button(self.cifdefs, wx.ID_CANCEL)
3220            btnsizer.AddButton(btn)
3221            btnsizer.Realize()
3222            vbox.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5)
3223            self.cifdefs.SetSizer(vbox)
3224            if not saveSize:
3225                vbox.Fit(self.cifdefs)
3226            self.cifdefs.Layout()
3227
3228        def OnToggleButton(event):
3229            'Respond to press of ToggleButton in SelectDisAglFlags'
3230            but = event.GetEventObject()
3231            if but.GetValue():
3232                but.DisAglSel[but.key] = True
3233            else:
3234                try:
3235                    del but.DisAglSel[but.key]
3236                except KeyError:
3237                    pass
3238        def keepTrue(event):
3239            event.GetEventObject().SetValue(True)
3240        def keepFalse(event):
3241            event.GetEventObject().SetValue(False)
3242
3243        def SelectDisAglFlags(event):
3244            'Select Distance/Angle use flags for the selected phase'
3245            phasenam = event.GetEventObject().phase
3246            phasedict = self.Phases[phasenam]
3247            SymOpList,offsetList,symOpList,G2oprList,G2opcodes = G2spc.AllOps(phasedict['General']['SGData'])
3248            generalData = phasedict['General']
3249            # create a dict for storing Pub flag for bonds/angles, if needed
3250            if phasedict['General'].get("DisAglHideFlag") is None:
3251                phasedict['General']["DisAglHideFlag"] = {}
3252            DisAngSel = phasedict['General']["DisAglHideFlag"]
3253
3254            cx,ct,cs,cia = phasedict['General']['AtomPtrs']
3255            cn = ct-1
3256            cfrac = cx+3
3257            DisAglData = {}
3258            # create a list of atoms, but skip atoms with zero occupancy
3259            xyz = []
3260            fpfx = str(phasedict['pId'])+'::Afrac:'
3261            for i,atom in enumerate(phasedict['Atoms']):
3262                if self.parmDict.get(fpfx+str(i),atom[cfrac]) == 0.0: continue
3263                xyz.append([i,]+atom[cn:cn+2]+atom[cx:cx+3])
3264            if 'DisAglCtls' not in generalData:
3265                # should not be used, since DisAglDialog should be called
3266                # for all phases before getting here
3267                dlg = G2G.DisAglDialog(
3268                    self.cifdefs,
3269                    {},
3270                    generalData)
3271                if dlg.ShowModal() == wx.ID_OK:
3272                    generalData['DisAglCtls'] = dlg.GetData()
3273                else:
3274                    dlg.Destroy()
3275                    return
3276                dlg.Destroy()
3277            dlg = wx.Dialog(
3278                self.G2frame,
3279                style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
3280            vbox = wx.BoxSizer(wx.VERTICAL)
3281            txt = wx.StaticText(dlg,wx.ID_ANY,'Searching distances for phase '+phasenam
3282                                +'\nPlease wait...')
3283            vbox.Add(txt,0,wx.ALL|wx.EXPAND)
3284            dlg.SetSizer(vbox)
3285            dlg.CenterOnParent()
3286            dlg.Show() # post "please wait"
3287            wx.BeginBusyCursor() # and change cursor
3288
3289            DisAglData['OrigAtoms'] = xyz
3290            DisAglData['TargAtoms'] = xyz
3291            SymOpList,offsetList,symOpList,G2oprList,G2opcodes = G2spc.AllOps(
3292                generalData['SGData'])
3293
3294#            xpandSGdata = generalData['SGData'].copy()
3295#            xpandSGdata.update({'SGOps':symOpList,
3296#                                'SGInv':False,
3297#                                'SGLatt':'P',
3298#                                'SGCen':np.array([[0, 0, 0]]),})
3299#            DisAglData['SGData'] = xpandSGdata
3300            DisAglData['SGData'] = generalData['SGData'].copy()
3301
3302            DisAglData['Cell'] = generalData['Cell'][1:] #+ volume
3303            if 'pId' in phasedict:
3304                DisAglData['pId'] = phasedict['pId']
3305                DisAglData['covData'] = self.OverallParms['Covariance']
3306            try:
3307                AtomLabels,DistArray,AngArray = G2stMn.RetDistAngle(
3308                    generalData['DisAglCtls'],
3309                    DisAglData)
3310            except KeyError:        # inside DistAngle for missing atom types in DisAglCtls
3311                print('**** ERROR - try again but do "Reset" to fill in missing atom types ****')
3312            wx.EndBusyCursor()
3313            txt.SetLabel('Set publication flags for distances and angles in\nphase '+phasenam)
3314            vbox.Add((5,5))
3315            vbox.Add(wx.StaticText(dlg,wx.ID_ANY,
3316                                   'The default is to flag all distances and angles as to be'+
3317                                   '\npublished. Change this by pressing appropriate buttons.'),
3318                     0,wx.ALL|wx.EXPAND)
3319            hbox = wx.BoxSizer(wx.HORIZONTAL)
3320            vbox.Add(hbox)
3321            hbox.Add(wx.StaticText(dlg,wx.ID_ANY,'Button appearance: '))
3322            but = wx.ToggleButton(dlg,wx.ID_ANY,'Publish')
3323            but.Bind(wx.EVT_TOGGLEBUTTON,keepFalse)
3324            hbox.Add(but)
3325            but = wx.ToggleButton(dlg,wx.ID_ANY,"Don't publish")
3326            but.Bind(wx.EVT_TOGGLEBUTTON,keepTrue)
3327            hbox.Add(but)
3328            but.SetValue(True)
3329            G2G.HorizontalLine(vbox,dlg)
3330
3331            cpnl = wxscroll.ScrolledPanel(dlg,size=(400,300))
3332            cbox = wx.BoxSizer(wx.VERTICAL)
3333            for c in sorted(DistArray):
3334                karr = []
3335                UsedCols = {}
3336                cbox.Add(wx.StaticText(cpnl,wx.ID_ANY,
3337                                   'distances to/angles around atom '+AtomLabels[c]))
3338                #dbox = wx.GridBagSizer(hgap=5)
3339                dbox = wx.GridBagSizer()
3340                for i,D in enumerate(DistArray[c]):
3341                    karr.append(tuple(D[0:3]))
3342                    val = "{:.2f}".format(D[3])
3343                    sym = " [{:d} {:d} {:d}]".format(*D[1]) + " #{:d}".format(D[2])
3344                    dbox.Add(wx.StaticText(cpnl,wx.ID_ANY,AtomLabels[D[0]]),
3345                             (i+1,0)
3346                             )
3347                    dbox.Add(wx.StaticText(cpnl,wx.ID_ANY,sym),
3348                             (i+1,1)
3349                             )
3350                    but = wx.ToggleButton(cpnl,wx.ID_ANY,val)
3351                    but.key = (c,karr[-1])
3352                    but.DisAglSel = DisAngSel
3353                    if DisAngSel.get(but.key): but.SetValue(True)
3354                    but.Bind(wx.EVT_TOGGLEBUTTON,OnToggleButton)
3355                    dbox.Add(but,(i+1,2),border=1)
3356                for i,D in enumerate(AngArray[c]):
3357                    val = "{:.1f}".format(D[2][0])
3358                    but = wx.ToggleButton(cpnl,wx.ID_ANY,val)
3359                    but.key = (karr[D[0]],c,karr[D[1]])
3360                    but.DisAglSel = DisAngSel
3361                    if DisAngSel.get(but.key): but.SetValue(True)
3362                    but.Bind(wx.EVT_TOGGLEBUTTON,OnToggleButton)
3363                    dbox.Add(but,(D[0]+1,D[1]+3),border=1)
3364                    UsedCols[D[1]+3] = True
3365                for i,D in enumerate(DistArray[c][:-1]): # label columns that are used
3366                    if UsedCols.get(i+3):
3367                        dbox.Add(wx.StaticText(cpnl,wx.ID_ANY,AtomLabels[D[0]]),
3368                                 (0,i+3),
3369                                 flag=wx.ALIGN_CENTER
3370                                 )
3371                dbox.Add(wx.StaticText(cpnl,wx.ID_ANY,'distance'),
3372                                 (0,2),
3373                                 flag=wx.ALIGN_CENTER
3374                                 )
3375                cbox.Add(dbox)
3376                G2G.HorizontalLine(cbox,cpnl)
3377            cpnl.SetSizer(cbox)
3378            cpnl.SetAutoLayout(1)
3379            cpnl.SetupScrolling()
3380            #cpnl.Bind(rw.EVT_RW_LAYOUT_NEEDED, self.OnLayoutNeeded) # needed if sizes change
3381            cpnl.Layout()
3382
3383            vbox.Add(cpnl, 1, wx.ALIGN_LEFT|wx.ALL|wx.EXPAND, 0)
3384
3385            btnsizer = wx.StdDialogButtonSizer()
3386            btn = wx.Button(dlg, wx.ID_OK, "Done")
3387            btn.SetDefault()
3388            btnsizer.AddButton(btn)
3389            btnsizer.Realize()
3390            vbox.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5)
3391            dlg.SetSizer(vbox)
3392            vbox.Fit(dlg)
3393            dlg.Layout()
3394
3395            dlg.CenterOnParent()
3396            dlg.ShowModal()
3397
3398#==============================================================================
3399####  _Exporter code starts here         ======================================
3400#==============================================================================
3401        # make sure required information is present
3402        self.CIFdate = dt.datetime.strftime(dt.datetime.now(),"%Y-%m-%dT%H:%M")
3403        if not self.CIFname: # Get a name for the CIF. If not defined, use the GPX name (save, if that is needed).
3404            if not self.G2frame.GSASprojectfile:
3405                self.G2frame.OnFileSaveas(None)
3406            if not self.G2frame.GSASprojectfile: return
3407            self.CIFname = os.path.splitext(
3408                os.path.split(self.G2frame.GSASprojectfile)[1]
3409                )[0]
3410            self.CIFname = self.CIFname.replace(' ','')
3411        # replace non-ASCII characters in CIFname with dots
3412        s = ''
3413        for c in self.CIFname:
3414            if ord(c) < 128:
3415                s += c
3416            else:
3417                s += '.'
3418        self.CIFname = s
3419        phasebyhistDict = {} # a cross-reference to phases by histogram -- sequential fits
3420        #=================================================================
3421        # write quick CIFs
3422        #=================================================================
3423        if phaseOnly: #====Phase only CIF ================================
3424            print('Writing CIF output to file '+self.filename)
3425            oneblock = True
3426            self.quickmode = True
3427            self.Write(' ')
3428            self.Write(70*'#')
3429            WriteCIFitem(self.fp, 'data_'+phaseOnly.replace(' ','_'))
3430            #phaseblk = self.Phases[phaseOnly] # pointer to current phase info
3431            # report the phase info
3432            if self.Phases[phaseOnly]['General']['Type'] == 'macromolecular':
3433                WritePhaseInfoMM(phaseOnly)
3434            else:
3435                WritePhaseInfo(phaseOnly)
3436            return
3437        elif histOnly: #====Histogram only CIF ================================
3438            print('Writing CIF output to file '+self.filename)
3439            hist = histOnly
3440            #histname = histOnly.replace(' ','')
3441            oneblock = True
3442            self.quickmode = True
3443            self.ifHKLF = False
3444            self.ifPWDR = True
3445            self.Write(' ')
3446            self.Write(70*'#')
3447            #phasenam = self.Phases.keys()[0]
3448            WriteCIFitem(self.fp, 'data_'+self.CIFname)
3449            if hist.startswith("PWDR"):
3450                WritePowderData(hist)
3451            elif hist.startswith("HKLF"):
3452                WriteSingleXtalData(hist)
3453            return
3454        #===============================================================================
3455        # setup for sequential fits here
3456        #===============================================================================
3457        seqmode = False
3458        seqHistList = self.G2frame.testSeqRefineMode()
3459        if seqHistList:
3460            if self.seqData is None:
3461                raise Exception('Use Export/Sequential project for sequential refinements')
3462            phaseWithHist = True   # include the phase in the same block as the histogram
3463            if len(self.Phases) > 1:
3464                phaseWithHist = False  # multiple phases per histogram
3465            seqmode = True
3466        #===============================================================================
3467        # the export process for a full CIF starts here (Sequential too)
3468        #===============================================================================
3469        # load saved CIF author name
3470        self.author = self.OverallParms['Controls'].get("Author",'?').strip()
3471        # initialize dict for Selection of Hist for unit cell reporting
3472        self.OverallParms['Controls']['CellHistSelection'] = self.OverallParms[
3473            'Controls'].get('CellHistSelection',{})
3474        self.CellHistSelection = self.OverallParms['Controls']['CellHistSelection']
3475        # create a dict with refined values and their uncertainties
3476        self.loadParmDict()
3477        # is there anything to export?
3478        if len(self.Phases) == len(self.powderDict) == len(self.xtalDict) == 0:
3479           self.G2frame.ErrorDialog(
3480               'Empty project',
3481               'Project does not contain any data or phases. Are they interconnected?')
3482           return
3483        if self.ExportSelect('ask'): return
3484        if not self.filename:
3485            print('No name supplied')
3486            return
3487        self.OpenFile()
3488       
3489        #if self.ExportSelect('default'): return
3490        # Someday: get restraint & constraint info
3491        #restraintDict = self.OverallParms.get('Restraints',{})
3492        #for i in  self.OverallParms['Constraints']:
3493        #    print i
3494        #    for j in self.OverallParms['Constraints'][i]:
3495        #        print j
3496
3497        self.quickmode = False # full CIF
3498        phasenam = None # include all phases
3499        # Will this require a multiblock CIF?
3500        if len(self.Phases) > 1:
3501            oneblock = False
3502        elif len(self.powderDict) + len(self.xtalDict) > 1:
3503            oneblock = False
3504        else: # one phase, one dataset, Full CIF
3505            oneblock = True
3506
3507        # check there is an instrument name for every histogram
3508        self.ifPWDR = False
3509        self.ifHKLF = False
3510        invalid = 0
3511        key3 = 'InstrName'
3512        for hist in self.Histograms:
3513            if hist.startswith("PWDR"):
3514                self.ifPWDR = True
3515                key2 = "Sample Parameters"
3516                d = self.Histograms[hist][key2]
3517            elif hist.startswith("HKLF"):
3518                self.ifHKLF = True
3519                key2 = "Instrument Parameters"
3520                d = self.Histograms[hist][key2][0]
3521            instrname = d.get(key3)
3522            if instrname is None:
3523                d[key3] = ''
3524                invalid += 1
3525            elif instrname.strip() == '':
3526                invalid += 1
3527            if hist.startswith("PWDR") and seqmode: break
3528        if invalid:
3529            #msg = ""
3530            #if invalid > 3: msg = (
3531            #    "\n\nNote: it may be faster to set the name for\n"
3532            #    "one histogram for each instrument and use the\n"
3533            #    "File/Copy option to duplicate the name"
3534            #    )
3535            if not EditInstNames(): return
3536
3537        # check for a distance-angle range search range for each phase
3538        for phasenam in sorted(self.Phases.keys()):
3539            #i = self.Phases[phasenam]['pId']
3540            phasedict = self.Phases[phasenam] # pointer to current phase info
3541            if 'DisAglCtls' not in phasedict['General']:
3542                dlg = G2G.DisAglDialog(
3543                    self.G2frame,
3544                    {},
3545                    phasedict['General'])
3546                if dlg.ShowModal() == wx.ID_OK:
3547                    phasedict['General']['DisAglCtls'] = dlg.GetData()
3548                else:
3549                    dlg.Destroy()
3550                    return
3551                dlg.Destroy()
3552                   
3553        # check if temperature values & pressure are defaulted
3554        default = 0
3555        for hist in self.Histograms:
3556            if hist.startswith("PWDR"):
3557                key2 = "Sample Parameters"
3558                T = self.Histograms[hist][key2].get('Temperature')
3559                if not T:
3560                    default += 1
3561                elif T == 300:
3562                    default += 1
3563                P = self.Histograms[hist][key2].get('Pressure')
3564                if not P:
3565                    default += 1
3566                elif P == 1:
3567                    default += 1
3568        if default > 0:
3569            dlg = wx.MessageDialog(
3570                self.G2frame,
3571                '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?',
3572                'Check T and P values',
3573                wx.OK|wx.CANCEL)
3574            ret = dlg.ShowModal()
3575            dlg.CenterOnParent()
3576            dlg.Destroy()
3577            if ret != wx.ID_OK: return
3578        if oneblock:
3579            # select a dataset to use (there should only be one set in one block,
3580            # but take whatever comes 1st)
3581            for hist in self.Histograms:
3582                histblk = self.Histograms[hist]
3583                if hist.startswith("PWDR"):
3584                    instnam = histblk["Sample Parameters"]['InstrName']
3585                    break # ignore all but 1st data histogram
3586                elif hist.startswith("HKLF"):
3587                    instnam = histblk["Instrument Parameters"][0]['InstrName']
3588                    break # ignore all but 1st data histogram
3589        # give the user a window to edit CIF contents
3590        if not self.author:
3591            self.author = self.OverallParms['Controls'].get("Author",'?').strip()
3592        if not self.author:
3593            if not EditAuthor(): return
3594        self.ValidateAscii([('Author name',self.author),]) # check for ASCII strings where needed, warn on problems
3595        self.shortauthorname = self.author.replace(',','').replace(' ','')[:20]
3596        self.cifdefs = wx.Dialog(
3597            self.G2frame,
3598            style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
3599        self.cifdefs.G2frame = self.G2frame
3600        self.cifdefs.CenterOnParent()
3601        EditCIFDefaults()
3602        if self.cifdefs.ShowModal() != wx.ID_OK:
3603            self.cifdefs.Destroy()
3604            return
3605        while self.ValidateAscii([('Author name',self.author),
3606                                  ]): # validate a few things as ASCII
3607            if self.cifdefs.ShowModal() != wx.ID_OK:
3608                self.cifdefs.Destroy()
3609                return
3610        self.cifdefs.Destroy()
3611        #======================================================================
3612        #---- Start writing the CIF - single block
3613        #======================================================================
3614        print('Writing CIF output to file '+self.filename+"...")
3615        if self.currentExportType == 'single' or self.currentExportType == 'powder':
3616            #======Data only CIF (powder/xtal) ====================================
3617            hist = self.histnam[0]
3618            self.CIFname = hist[5:40].replace(' ','')
3619            WriteCIFitem(self.fp, 'data_'+self.CIFname)
3620            if hist.startswith("PWDR"):
3621                WritePowderData(hist)
3622            elif hist.startswith("HKLF"):
3623                WriteSingleXtalData(hist)
3624            else:
3625                print ("should not happen")
3626        elif oneblock:
3627            #====Single block, data & phase CIF ===================================
3628            WriteCIFitem(self.fp, 'data_'+self.CIFname)
3629            if phasenam is None: # if not already selected, select the first phase (should be one)
3630                phasenam = self.Phases.keys()[0]
3631            #print 'phasenam',phasenam
3632            #phaseblk = self.Phases[phasenam] # pointer to current phase info
3633            instnam = instnam.replace(' ','')
3634            WriteCIFitem(self.fp, '_pd_block_id',
3635                         str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
3636                         str(self.shortauthorname) + "|" + instnam)
3637            WriteAudit()
3638            writeCIFtemplate(self.OverallParms['Controls'],'publ') # overall (publication) template
3639            WriteOverall()
3640            writeCIFtemplate(self.Phases[phasenam]['General'],'phase',phasenam) # write phase template
3641            # report the phase info
3642            if self.Phases[phasenam]['General']['Type'] == 'macromolecular':
3643                WritePhaseInfoMM(phasenam,False)
3644            else:
3645                WritePhaseInfo(phasenam,False)
3646            if hist.startswith("PWDR"):  # this is invoked for single-block CIFs
3647                # preferred orientation
3648                SH = FormatSH(phasenam)
3649                MD = FormatHAPpo(phasenam)
3650                if SH and MD:
3651                    WriteCIFitem(self.fp, '_pd_proc_ls_pref_orient_corr', SH + '\n' + MD)
3652                elif SH or MD:
3653                    WriteCIFitem(self.fp, '_pd_proc_ls_pref_orient_corr', SH + MD)
3654                else:
3655                    WriteCIFitem(self.fp, '_pd_proc_ls_pref_orient_corr', 'none')
3656                # report profile, since one-block: include both histogram and phase info (N.B. there is only 1 of each)
3657                WriteCIFitem(self.fp, '_pd_proc_ls_profile_function',
3658                    FormatInstProfile(histblk["Instrument Parameters"],histblk['hId'])
3659                    +'\n'+FormatPhaseProfile(phasenam))
3660
3661                histblk = self.Histograms[hist]["Sample Parameters"]
3662                writeCIFtemplate(histblk,'powder',histblk['InstrName']) # write powder template
3663                WritePowderData(hist)
3664            elif hist.startswith("HKLF"):
3665                histprm = self.Histograms[hist]["Instrument Parameters"][0]
3666                writeCIFtemplate(histprm,'single',histprm['InstrName']) # single crystal template
3667                WriteSingleXtalData(hist)
3668        elif seqHistList:
3669            #---- sequential fit project (multiblock)  ====================
3670            for phasenam in sorted(self.Phases.keys()):
3671                rId = phasedict['ranId']
3672                if rId in self.CellHistSelection: continue
3673                self.CellHistSelection[rId] = self._CellSelectHist(phasenam)
3674            nsteps = 1 + len(self.Phases) + len(self.powderDict) + len(self.xtalDict)
3675            try:
3676                dlg = wx.ProgressDialog('CIF progress','starting',nsteps,parent=self.G2frame)
3677                dlg.CenterOnParent()
3678
3679                # publication info block
3680                step = 1
3681                dlg.Update(step,"Exporting overall section")
3682                WriteCIFitem(self.fp, '\ndata_'+self.CIFname+'_publ')
3683                WriteAudit()
3684                WriteCIFitem(self.fp, '_pd_block_id',
3685                             str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
3686                             str(self.shortauthorname) + "|Overall")
3687                writeCIFtemplate(self.OverallParms['Controls'],'publ') #insert the publication template
3688                # ``template_publ.cif`` or a modified version
3689               
3690                # overall info block
3691                WriteCIFitem(self.fp, 'data_'+str(self.CIFname)+'_overall')
3692                WriteOverall('seq')
3693                hist = self.powderDict[sorted(self.powderDict.keys())[0]]
3694                instnam = self.Histograms[hist]["Sample Parameters"]['InstrName']
3695                writeCIFtemplate(self.seqSmplParms,'powder',instnam) # powder template
3696                instnam = instnam.replace(' ','')
3697                #============================================================
3698                if phaseWithHist:
3699                    WriteCIFitem(self.fp, '# POINTERS TO HISTOGRAM BLOCKS (Phase in histogram block)')
3700                else:
3701                    WriteCIFitem(self.fp, '# POINTERS TO HISTOGRAM BLOCKS (Phases pointer in histogram block)') 
3702                datablockidDict = {} # save block names here
3703                # loop over data blocks
3704                if len(self.powderDict) + len(self.xtalDict) > 1:
3705                    loopprefix = ''
3706                    WriteCIFitem(self.fp, 'loop_   _pd_block_diffractogram_id')
3707                else:
3708                    loopprefix = '_pd_block_diffractogram_id'
3709                for i in sorted(self.powderDict.keys()):
3710                    hist = self.powderDict[i]
3711                    j = self.Histograms[hist]['hId']
3712                    datablockidDict[hist] = (str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
3713                                             str(self.shortauthorname) + "|" +
3714                                             instnam + "_hist_"+str(j))
3715                    WriteCIFitem(self.fp, loopprefix,datablockidDict[hist])
3716                for i in sorted(self.xtalDict.keys()):
3717                    hist = self.xtalDict[i]
3718                    histblk = self.Histograms[hist]
3719                    instnam = histblk["Instrument Parameters"][0]['InstrName']
3720                    instnam = instnam.replace(' ','')
3721                    i = histblk['hId']
3722                    datablockidDict[hist] = (str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
3723                                             str(self.shortauthorname) + "|" +
3724                                             instnam + "_hist_"+str(i))
3725                    WriteCIFitem(self.fp, loopprefix,datablockidDict[hist])
3726                # setup and show sequential results table
3727                tblLabels,tblValues,tblSigs,tblTypes = mkSeqResTable('cif',seqHistList,self.seqData,
3728                                                    self.Phases,self.Histograms,self.Controls)
3729                WriteCIFitem(self.fp, '\n# Sequential results table') # (in case anyone can make sense of it)
3730                WriteCIFitem(self.fp, 'loop_   _gsas_seq_results_col_num _gsas_seq_results_col_label')
3731                for i,lbl in enumerate(tblLabels):
3732                    s = PutInCol(str(i),5)
3733                    if ' ' in lbl:
3734                        s += '"' + lbl + '"'
3735                    else:
3736                        s += lbl
3737                    WriteCIFitem(self.fp,"  "+s)
3738                s = 'loop_ '
3739                linelength = 120
3740                for i in range(len(tblLabels)):
3741                    if len(s) > linelength:
3742                        WriteCIFitem(self.fp,s)
3743                        s = '  '
3744                    s += " _gsas_seq_results_val" + str(i)
3745                WriteCIFitem(self.fp,s)
3746               
3747                for r in range(len(tblValues[0])):
3748                    s = ''
3749                    for c in range(len(tblLabels)):
3750                        if len(s) > linelength:
3751                            WriteCIFitem(self.fp,s)
3752                            s = '  '
3753                        sig = None
3754                        if tblSigs[c] is not None:
3755                            sig = tblSigs[c][r]
3756                           
3757                        if tblValues[c][r] is None:
3758                            if tblTypes[c] == 'int':
3759                                wid = 5
3760                            elif tblTypes[c] == 'str':
3761                                wid = 10
3762                            else:
3763                                wid = 12                               
3764                            s += PutInCol('.',wid)
3765                        elif sig is None and ',' in tblTypes[c]:
3766                            s += PutInCol(
3767                                ('{{:{}.{}f}}'.format(*tblTypes[c].split(','))).format(tblValues[c][r]),12)
3768                        elif tblTypes[c] == 'int':
3769                            s += PutInCol(str(tblValues[c][r]),5)
3770                        elif tblTypes[c] == 'str':
3771                            s += PutInCol(str(tblValues[c][r]),10)
3772                        elif sig is None and ',' in tblTypes[c]:
3773                            s += PutInCol(
3774                                ('{{:{}.{}f}}'.format(*tblTypes[c].split(','))).format(tblValues[c][r]),12)
3775                        elif sig is None and tblTypes[c] == 'float':
3776                            s += PutInCol('{:.6g}'.format(tblValues[c][r]),12)
3777                        elif sig:
3778                            s += PutInCol(G2mth.ValEsd(tblValues[c][r],sig),12)
3779                        else:
3780                            s += PutInCol(str(tblValues[c][r]),15)
3781                    WriteCIFitem(self.fp,s+'\n')
3782
3783                # sample template info: a block for each phase in project
3784                i = sorted(self.powderDict.keys())[0]
3785                hist = self.powderDict[i]
3786                histblk = self.Histograms[hist]
3787                if phaseWithHist:    # include sample info in overall block
3788                    phasenam = list(self.Phases.keys())[0]
3789                    writeCIFtemplate(self.Phases[phasenam]['General'],'phase',phasenam) # write phase template
3790                    WriteSeqOverallPhaseInfo(phasenam,histblk)
3791                else:
3792                    for j,phasenam in enumerate(sorted(self.Phases.keys())):
3793                        pId = self.Phases[phasenam]['pId']
3794                        WriteCIFitem(self.fp, '\n#'+78*'=')
3795                        WriteCIFitem(self.fp, 'data_'+self.CIFname+"_overall_phase"+str(j)+'\n')
3796                        writeCIFtemplate(self.Phases[phasenam]['General'],'phase',phasenam) # write phase template
3797                        WriteSeqOverallPhaseInfo(phasenam,histblk)
3798
3799                # create a block for each histogram, include phase in block for one-phase refinements
3800                # or separate blocks for each phase & histogram if more than one phase
3801                for i in sorted(self.powderDict.keys()):
3802                    hist = self.powderDict[i]
3803                    print('processing hist #',i,hist)
3804                    hId = self.Histograms[hist]['hId']
3805                    dlg.Update(step,"Exporting "+hist.strip())
3806                    histblk = self.Histograms[hist]
3807                    WriteCIFitem(self.fp, '# Information for histogram '+str(i)+': '+hist)
3808                    WriteCIFitem(self.fp, '\ndata_'+self.CIFname+"_pwd_"+str(i))
3809                    WriteCIFitem(self.fp, '_pd_block_id',datablockidDict[hist])
3810                    if not phaseWithHist:
3811                        WriteCIFitem(self.fp, '\n# POINTERS TO PHASE BLOCKS')
3812                        phaseBlockName = {}
3813
3814                        wtFrSum = 0.
3815                        for j,phasenam in enumerate(sorted(self.Phases.keys())):
3816                            if hist not in self.Phases[phasenam]['Histograms']: continue
3817                            if not self.Phases[phasenam]['Histograms'][hist]['Use']: continue
3818                            phFrac = self.Phases[phasenam]['Histograms'][hist]['Scale'][0]
3819                            phFracKey = str(self.Phases[phasenam]['pId'])+':'+str(hId)+':Scale'
3820                            phFrac = self.seqData[hist]['parmDict'].get(phFracKey,phFrac)
3821                            wtFrSum += phFrac * self.Phases[phasenam]['General']['Mass']
3822                        WriteCIFitem(self.fp, 'loop_ _pd_phase_id _pd_phase_block_id _pd_phase_mass_%')
3823                        for j,phasenam in enumerate(sorted(self.Phases.keys())):
3824                            pId = self.Phases[phasenam]['pId']
3825                            if hist not in self.Phases[phasenam]['Histograms']: continue
3826                            if not self.Phases[phasenam]['Histograms'][hist]['Use']: continue
3827                            if ' ' in phasenam:
3828                                s = PutInCol('"'+phasenam+'"',20)
3829                            else:
3830                                s = PutInCol(phasenam,20)
3831                            phaseBlockName[pId] = datablockidDict[hist]+'_p'+str(j+1)
3832                            phFrac = self.Phases[phasenam]['Histograms'][hist]['Scale'][0]
3833                            phFracKey = str(self.Phases[phasenam]['pId'])+':'+str(hId)+':Scale'
3834                            phFrac = self.seqData[hist]['parmDict'].get(phFracKey,phFrac)
3835                            wtFr = phFrac * self.Phases[phasenam]['General']['Mass'] / wtFrSum
3836                            if phFracKey in self.seqData[hist]['varyList']:
3837                                sig = self.seqData[hist]['sig'][self.seqData[hist]['varyList'].index(phFracKey)]
3838                                sig *= self.Phases[phasenam]['General']['Mass'] / wtFrSum
3839                            elif phFracKey in self.seqData[hist]['depParmDict']:
3840                                sig = self.seqData[hist]['depParmDict'][phFracKey][1]
3841                                sig *= self.Phases[phasenam]['General']['Mass'] / wtFrSum
3842                            else:
3843                                sig = -0.0001
3844                            WriteCIFitem(self.fp, "  "+ s + " " + phaseBlockName[pId] + "  " + G2mth.ValEsd(wtFr,sig))
3845                        PP = FormatInstProfile(histblk["Instrument Parameters"],histblk['hId'])
3846                        PP += '\n'
3847                        WriteCIFitem(self.fp, '_pd_proc_ls_profile_function',PP)
3848               
3849                    WritePowderData(hist,seq=True) # write background, data & reflections, some instrument & sample terms
3850                    WriteCIFitem(self.fp, '\n# PHASE INFO FOR HISTOGRAM '+hist)
3851                    # loop over phases, add a block header if there is more than one phase
3852                    for j,phasenam in enumerate(sorted(self.Phases.keys())):
3853                        pId = self.Phases[phasenam]['pId']
3854                        if hist not in self.Phases[phasenam]['Histograms']: continue
3855                        if not self.Phases[phasenam]['Histograms'][hist]['Use']: continue
3856                        WriteCIFitem(self.fp, '\n# phase info for '+str(phasenam) + ' follows')
3857                        if not phaseWithHist:
3858                            WriteCIFitem(self.fp, 'data_'+self.CIFname+"_hist"+str(i)+"_phase"+str(j))
3859                            WriteCIFitem(self.fp, '_pd_block_id',phaseBlockName[pId])
3860                            WriteCIFitem(self.fp, '')
3861
3862                        WriteSeqPhaseVals(phasenam,self.Phases[phasenam],pId,hist)
3863
3864                        # preferred orientation & profile terms
3865                        if self.ifPWDR:
3866                            #SH = FormatSH(phasenam)     # TODO: needs to use seqData
3867                            #MD = FormatHAPpo(phasenam)  # TODO: switch to seqData
3868                            #if SH and MD:
3869                            #    WriteCIFitem(self.fp, '_pd_proc_ls_pref_orient_corr', SH + '\n' + MD)
3870                            #elif SH or MD:
3871                            #    WriteCIFitem(self.fp, '_pd_proc_ls_pref_orient_corr', SH + MD)
3872                            #else:
3873                            #    WriteCIFitem(self.fp, '_pd_proc_ls_pref_orient_corr', 'none')
3874                            # report sample profile terms for all histograms with current phase
3875                            if phaseWithHist:
3876                                PP = FormatInstProfile(histblk["Instrument Parameters"],histblk['hId'])
3877                                PP += '\n'
3878                            else:
3879                                PP = ''
3880                            PP += FormatPhaseProfile(phasenam,hist)
3881                            WriteCIFitem(self.fp, '\n_pd_proc_ls_profile_function',PP)
3882            finally:
3883                dlg.Destroy()
3884        else:
3885            #---- multiblock: multiple phases and/or histograms ====================
3886            for phasenam in sorted(self.Phases.keys()):
3887                rId = phasedict['ranId']
3888                if rId in self.CellHistSelection: continue
3889                self.CellHistSelection[rId] = self._CellSelectHist(phasenam)
3890            nsteps = 1 + len(self.Phases) + len(self.powderDict) + len(self.xtalDict)
3891            try:
3892                dlg = wx.ProgressDialog('CIF progress','starting',nsteps,parent=self.G2frame)
3893                dlg.CenterOnParent()
3894
3895                # publication info
3896                step = 1
3897                dlg.Update(step,"Exporting overall section")
3898                WriteCIFitem(self.fp, '\ndata_'+self.CIFname+'_publ')
3899                WriteAudit()
3900                WriteCIFitem(self.fp, '_pd_block_id',
3901                             str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
3902                             str(self.shortauthorname) + "|Overall")
3903                writeCIFtemplate(self.OverallParms['Controls'],'publ') #insert the publication template
3904                # ``template_publ.cif`` or a modified version
3905                # overall info
3906                WriteCIFitem(self.fp, 'data_'+str(self.CIFname)+'_overall')
3907                WriteOverall()
3908                #============================================================
3909                WriteCIFitem(self.fp, '# POINTERS TO PHASE AND HISTOGRAM BLOCKS')
3910                datablockidDict = {} # save block names here -- N.B. check for conflicts between phase & hist names (unlikely!)
3911                # loop over phase blocks
3912                if len(self.Phases) > 1:
3913                    loopprefix = ''
3914                    WriteCIFitem(self.fp, 'loop_   _pd_phase_block_id')
3915                else:
3916                    loopprefix = '_pd_phase_block_id'
3917
3918                for phasenam in sorted(self.Phases.keys()):
3919                    i = self.Phases[phasenam]['pId']
3920                    datablockidDict[phasenam] = (str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
3921                                 'phase_'+ str(i) + '|' + str(self.shortauthorname))
3922                    WriteCIFitem(self.fp, loopprefix,datablockidDict[phasenam])
3923                # loop over data blocks
3924                if len(self.powderDict) + len(self.xtalDict) > 1:
3925                    loopprefix = ''
3926                    WriteCIFitem(self.fp, 'loop_   _pd_block_diffractogram_id')
3927                else:
3928                    loopprefix = '_pd_block_diffractogram_id'
3929                for i in sorted(self.powderDict.keys()):
3930                    hist = self.powderDict[i]
3931                    histblk = self.Histograms[hist]
3932                    instnam = histblk["Sample Parameters"]['InstrName']
3933                    instnam = instnam.replace(' ','')
3934                    j = histblk['hId']
3935                    datablockidDict[hist] = (str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
3936                                             str(self.shortauthorname) + "|" +
3937                                             instnam + "_hist_"+str(j))
3938                    WriteCIFitem(self.fp, loopprefix,datablockidDict[hist])
3939                for i in sorted(self.xtalDict.keys()):
3940                    hist = self.xtalDict[i]
3941                    histblk = self.Histograms[hist]
3942                    instnam = histblk["Instrument Parameters"][0]['InstrName']
3943                    instnam = instnam.replace(' ','')
3944                    i = histblk['hId']
3945                    datablockidDict[hist] = (str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
3946                                             str(self.shortauthorname) + "|" +
3947                                             instnam + "_hist_"+str(i))
3948                    WriteCIFitem(self.fp, loopprefix,datablockidDict[hist])
3949                #============================================================
3950                # loop over phases, exporting them
3951                for j,phasenam in enumerate(sorted(self.Phases.keys())):
3952                    step += 1
3953                    dlg.Update(step,"Exporting phase "+phasenam+' (#'+str(j+1)+')')
3954                    i = self.Phases[phasenam]['pId']
3955                    WriteCIFitem(self.fp, '\ndata_'+self.CIFname+"_phase_"+str(i))
3956                    WriteCIFitem(self.fp, '# Information for phase '+str(i))
3957                    WriteCIFitem(self.fp, '_pd_block_id',datablockidDict[phasenam])
3958                    # report the phase
3959                    writeCIFtemplate(self.Phases[phasenam]['General'],'phase',phasenam) # write phase template
3960                    if self.Phases[phasenam]['General']['Type'] == 'macromolecular':
3961                        WritePhaseInfoMM(phasenam,False,False)
3962                    else:
3963                        WritePhaseInfo(phasenam,False,False)
3964                    # preferred orientation
3965                    if self.ifPWDR:
3966                        SH = FormatSH(phasenam)
3967                        MD = FormatHAPpo(phasenam)
3968                        if SH and MD:
3969                            WriteCIFitem(self.fp, '_pd_proc_ls_pref_orient_corr', SH + '\n' + MD)
3970                        elif SH or MD:
3971                            WriteCIFitem(self.fp, '_pd_proc_ls_pref_orient_corr', SH + MD)
3972                        else:
3973                            WriteCIFitem(self.fp, '_pd_proc_ls_pref_orient_corr', 'none')
3974                    # report sample profile terms for all histograms with current phase
3975                    PP = FormatPhaseProfile(phasenam)
3976                    if PP:
3977                        WriteCIFitem(self.fp, '_pd_proc_ls_profile_function',PP)
3978                    self.ShowHstrainCells(phasenam,datablockidDict)
3979
3980                #============================================================
3981                # loop over histograms, exporting them
3982                # first, get atoms across all phases
3983                uniqueAtoms = []
3984                for phasenam in self.Phases:
3985                    cx,ct,cs,cia = self.Phases[phasenam]['General']['AtomPtrs']
3986                    for line in self.Phases[phasenam]['Atoms']:
3987                        atype = line[ct].strip()
3988                        if atype.find('-') != -1: atype = atype.split('-')[0]
3989                        if atype.find('+') != -1: atype = atype.split('+')[0]
3990                        atype = atype[0].upper()+atype[1:2].lower() # force case conversion
3991                        if atype == "D" or atype == "D": atype = "H"
3992                        if atype not in uniqueAtoms:
3993                            uniqueAtoms.append(atype)
3994
3995                for i in sorted(self.powderDict.keys()):
3996                    hist = self.powderDict[i]
3997                    histblk = self.Histograms[hist]
3998                    if hist.startswith("PWDR"):
3999                        step += 1
4000                        dlg.Update(step,"Exporting "+hist.strip())
4001                        WriteCIFitem(self.fp, '\ndata_'+self.CIFname+"_pwd_"+str(i))
4002                        WriteCIFitem(self.fp, '# Information for histogram '+str(i)+': '+hist)
4003                        #instnam = histblk["Sample Parameters"]['InstrName']
4004                        # report instrumental profile terms
4005                        WriteCIFitem(self.fp, '_pd_proc_ls_profile_function',
4006                            FormatInstProfile(histblk["Instrument Parameters"],histblk['hId']))
4007                        WriteCIFitem(self.fp, '_pd_block_id',datablockidDict[hist])
4008                        histprm = self.Histograms[hist]["Sample Parameters"]
4009                        writeCIFtemplate(histprm,'powder',histprm['InstrName']) # powder template
4010
4011                        # get xray wavelength and compute & write f' & f''
4012                        lam = None
4013                        if 'X' in histblk['Instrument Parameters'][0]['Type'][0]:
4014                            for k in ('Lam','Lam1'):
4015                                if k in histblk['Instrument Parameters'][0]:
4016                                    lam = histblk['Instrument Parameters'][0][k][0]
4017                                    break
4018                        if lam:
4019                            keV = 12.397639/lam
4020                            WriteCIFitem(self.fp,'loop_')
4021                            for item in ('_atom_type_symbol','_atom_type_scat_dispersion_real',
4022                                             '_atom_type_scat_dispersion_imag','_atom_type_scat_dispersion_source'):
4023                                WriteCIFitem(self.fp,'     '+item)
4024                            for elem in HillSortElements(uniqueAtoms):
4025                                s = '  '
4026                                s += PutInCol(elem,4)
4027                                Orbs = G2el.GetXsectionCoeff(elem)
4028                                FP,FPP,Mu = G2el.FPcalc(Orbs, keV)
4029                                s += {:8.3f}{:8.3f}   https://subversion.xray.aps.anl.gov/pyGSAS/trunk/atmdata.py'.format(FP,FPP)
4030                                WriteCIFitem(self.fp,s.rstrip())
4031                            WriteCIFitem(self.fp,'')
4032                        WritePowderData(hist)
4033                for i in sorted(self.xtalDict.keys()):
4034                    hist = self.xtalDict[i]
4035                    histblk = self.Histograms[hist]
4036                    if hist.startswith("HKLF"):
4037                        step += 1
4038                        dlg.Update(step,"Exporting "+hist.strip())
4039                        WriteCIFitem(self.fp, '\ndata_'+self.CIFname+"_sx_"+str(i))
4040                        #instnam = histblk["Instrument Parameters"][0]['InstrName']
4041                        WriteCIFitem(self.fp, '# Information for histogram '+str(i)+': '+hist)
4042                        WriteCIFitem(self.fp, '_pd_block_id',datablockidDict[hist])
4043                        histprm = self.Histograms[hist]["Instrument Parameters"][0]
4044                        writeCIFtemplate(histprm,'single',histprm['InstrName']) # single crystal template
4045                        WriteSingleXtalData(hist)
4046            finally:
4047                dlg.Destroy()
4048
4049        WriteCIFitem(self.fp, '#--' + 15*'eof--' + '#')
4050        print("...export completed")
4051        print('file '+self.fullpath)
4052        # end of CIF export
4053
4054class ExportProjectCIF(ExportCIF):
4055    '''Used to create a CIF of an entire project
4056
4057    :param wx.Frame G2frame: reference to main GSAS-II frame
4058    '''
4059    def __init__(self,G2frame):
4060        ExportCIF.__init__(self,
4061            G2frame=G2frame,
4062            formatName = 'Full CIF',
4063            extension='.cif',
4064            longFormatName = 'Export project as CIF'
4065            )
4066        self.exporttype = ['project']
4067
4068    def Exporter(self,event=None,seqData=None,Controls=None):
4069        self.seqData = seqData
4070        self.Controls = Controls
4071        self.InitExport(event)
4072        # load all of the tree into a set of dicts
4073        self.loadTree()
4074        self._Exporter(event=event)
4075        self.CloseFile()
4076
4077class ExportPhaseCIF(ExportCIF):
4078    '''Used to create a simple CIF with one phase. Uses exact same code as
4079    :class:`ExportCIF` except that `phaseOnly` is set for the Exporter
4080    Shows up in menu as Quick CIF.
4081
4082    :param wx.Frame G2frame: reference to main GSAS-II frame
4083    '''
4084    def __init__(self,G2frame):
4085        ExportCIF.__init__(self,
4086            G2frame=G2frame,
4087            formatName = 'Quick CIF',
4088            extension='.cif',
4089            longFormatName = 'Export one phase in CIF'
4090            )
4091        self.exporttype = ['phase']
4092        # CIF-specific items
4093        self.author = ''
4094
4095    def Exporter(self,event=None):
4096        # get a phase and file name
4097        # the export process starts here
4098        self.InitExport(event)
4099        # load all of the tree into a set of dicts
4100        self.loadTree()
4101        # create a dict with refined values and their uncertainties
4102        self.loadParmDict()
4103        self.multiple = True
4104        self.currentExportType = 'phase'
4105        if self.ExportSelect('ask'): return
4106        self.OpenFile()
4107        for name in self.phasenam:
4108            self._Exporter(event=event,phaseOnly=name)  #TODO: repeat for magnetic phase
4109        self.CloseFile()
4110
4111    def Writer(self,hist,phasenam,mode='w'):
4112        # set the project file name
4113        self.CIFname = os.path.splitext(
4114            os.path.split(self.G2frame.GSASprojectfile)[1]
4115            )[0]+'_'+phasenam+'_'+hist
4116        self.CIFname = self.CIFname.replace(' ','')
4117        self.OpenFile(mode=mode)
4118        self._Exporter(phaseOnly=phasenam)
4119        self.CloseFile()
4120
4121class ExportPwdrCIF(ExportCIF):
4122    '''Used to create a simple CIF containing diffraction data only. Uses exact same code as
4123    :class:`ExportCIF` except that `histOnly` is set for the Exporter
4124    Shows up in menu as Quick CIF.
4125
4126    :param wx.Frame G2frame: reference to main GSAS-II frame
4127    '''
4128    def __init__(self,G2frame):
4129        ExportCIF.__init__(self,
4130            G2frame=G2frame,
4131            formatName = 'Data-only CIF',
4132            extension='.cif',
4133            longFormatName = 'Export data as CIF'
4134            )
4135        if G2frame is None: raise AttributeError('CIF export requires data tree') # prevent use from Scriptable
4136        self.exporttype = ['powder']
4137        # CIF-specific items
4138        self.author = ''
4139
4140    def Exporter(self,event=None):
4141        self.InitExport(event)
4142        # load all of the tree into a set of dicts
4143        self.currentExportType = None
4144        self.loadTree()
4145        self.currentExportType = 'powder'
4146        # create a dict with refined values and their uncertainties
4147        self.loadParmDict()
4148        self.multiple = False
4149        if self.ExportSelect( # set export parameters
4150            AskFile='ask' # get a file name/directory to save in
4151            ): return
4152        self.OpenFile()
4153        self._Exporter(event=event,histOnly=self.histnam[0])
4154
4155    def Writer(self,hist,mode='w'):
4156        '''Used for histogram CIF export of a sequential fit.
4157        '''
4158        # set the project file name
4159        self.CIFname = os.path.splitext(
4160            os.path.split(self.G2frame.GSASprojectfile)[1]
4161            )[0]+'_'+hist
4162        self.CIFname = self.CIFname.replace(' ','')
4163        self.OpenFile(mode=mode)
4164        self._Exporter(histOnly=hist)
4165        if mode == 'w':
4166            print('CIF written to file '+self.fullpath)
4167        self.CloseFile()
4168
4169class ExportHKLCIF(ExportCIF):
4170    '''Used to create a simple CIF containing diffraction data only. Uses exact same code as
4171    :class:`ExportCIF` except that `histOnly` is set for the Exporter
4172    Shows up in menu as Quick CIF.
4173
4174    :param wx.Frame G2frame: reference to main GSAS-II frame
4175    '''
4176    def __init__(self,G2frame):
4177        ExportCIF.__init__(self,
4178            G2frame=G2frame,
4179            formatName = 'Data-only CIF',
4180            extension='.cif',
4181            longFormatName = 'Export data as CIF'
4182            )
4183        self.exporttype = ['single']
4184        # CIF-specific items
4185        self.author = ''
4186
4187    def Exporter(self,event=None):
4188        self.InitExport(event)
4189        # load all of the tree into a set of dicts
4190        self.currentExportType = None
4191        self.loadTree()
4192        self.currentExportType = 'single'
4193        # create a dict with refined values and their uncertainties
4194        self.loadParmDict()
4195        self.multiple = False
4196        if self.ExportSelect( # set export parameters
4197            AskFile='ask' # get a file name/directory to save in
4198            ): return
4199        self.OpenFile()
4200        self._Exporter(event=event,histOnly=self.histnam[0])
4201
4202#===============================================================================
4203# misc CIF utilities
4204#===============================================================================
4205def PickleCIFdict(fil):
4206    '''Loads a CIF dictionary, cherry picks out the items needed
4207    by local code and sticks them into a python dict and writes
4208    that dict out as a pickle file for later reuse.
4209    If the write fails a warning message is printed,
4210    but no exception occurs.
4211
4212    :param str fil: file name of CIF dictionary, will usually end
4213      in .dic
4214    :returns: the dict with the definitions
4215    '''
4216    import CifFile as cif # PyCifRW from James Hester
4217    cifdic = {}
4218    try:
4219        fp = open(fil,'r')             # patch: open file to avoid windows bug
4220        dictobj = cif.CifDic(fp)
4221        fp.close()
4222    except IOError:
4223        dictobj = cif.CifDic(fil)
4224    if DEBUG: print('loaded '+fil)
4225    for item in dictobj.keys():
4226        cifdic[item] = {}
4227        for j in (
4228            '_definition','_type',
4229            '_enumeration',
4230            '_enumeration_detail',
4231            '_enumeration_range'):
4232            if dictobj[item].get(j):
4233                cifdic[item][j] = dictobj[item][j]
4234    try:
4235        fil = os.path.splitext(fil)[0]+'.cpickle'
4236        fp = open(fil,'wb')
4237        pickle.dump(cifdic,fp)
4238        fp.close()
4239        if DEBUG: print('wrote '+fil)
4240    except:
4241        print ('Unable to write '+fil)
4242    return cifdic
4243
4244def LoadCIFdic():
4245    '''Create a composite core+powder CIF lookup dict containing
4246    information about all items in the CIF dictionaries, loading
4247    pickled files if possible. The routine looks for files
4248    named cif_core.cpickle and cif_pd.cpickle in every
4249    directory in the path and if they are not found, files
4250    cif_core.dic and/or cif_pd.dic are read.
4251
4252    :returns: the dict with the definitions
4253    '''
4254    cifdic = {}
4255    for ftyp in "cif_core","cif_pd":
4256        for loc in sys.path:
4257            fil = os.path.join(loc,ftyp+".cpickle")
4258            if not os.path.exists(fil): continue
4259            fp = open(fil,'rb')
4260            try:
4261                cifdic.update(pickle.load(fp))
4262                if DEBUG: print('reloaded '+fil)
4263                break
4264            finally:
4265                fp.close()
4266        else:
4267            for loc in sys.path:
4268                fil = os.path.join(loc,ftyp+".dic")
4269                if not os.path.exists(fil): continue
4270                #try:
4271                if True:
4272                    cifdic.update(PickleCIFdict(fil))
4273                    break
4274                #except:
4275                #    pass
4276            else:
4277                print('Could not load '+ftyp+' dictionary')
4278    return cifdic
4279
4280class CIFdefHelp(wx.Button):
4281    '''Create a help button that displays help information on
4282    the current data item
4283
4284    :param parent: the panel which will be the parent of the button
4285    :param str msg: the help text to be displayed
4286    :param wx.Dialog helpwin: Frame for CIF editing dialog
4287    :param wx.TextCtrl helptxt: TextCtrl where help text is placed
4288    '''
4289    def __init__(self,parent,msg,helpwin,helptxt):
4290        wx.Button.__init__(self,parent,wx.ID_HELP)
4291        self.Bind(wx.EVT_BUTTON,self._onPress)
4292        self.msg=msg
4293        self.parent = parent
4294        self.helpwin = helpwin
4295        self.helptxt = helptxt
4296    def _onPress(self,event):
4297        'Respond to a button press by displaying the requested text'
4298        try:
4299            ww,wh = self.helpwin.GetSize()
4300            ow,oh = self.helptxt.GetSize()
4301            self.helptxt.SetLabel(self.msg)
4302            self.helptxt.Wrap(ww-10)
4303            w,h = self.helptxt.GetSize()
4304            if h > oh: # resize the help area if needed, but avoid changing width
4305                self.helptxt.SetMinSize((ww,h))
4306                self.helpwin.GetSizer().Fit(self.helpwin)
4307        except: # error posting help, ignore
4308            return
4309
4310def CIF2dict(cf):
4311    '''copy the contents of a CIF out from a PyCifRW block object
4312    into a dict
4313
4314    :returns: cifblk, loopstructure where cifblk is a dict with
4315      CIF items and loopstructure is a list of lists that defines
4316      which items are in which loops.
4317    '''
4318    blk = cf.keys()[0] # assume templates are a single CIF block, use the 1st
4319    try:
4320        loopstructure = cf[blk].loopnames()[:] # copy over the list of loop contents
4321    except AttributeError:
4322        loopstructure = [j[:] for j in cf[blk].loops.values()] # method replaced?
4323    dblk = {}
4324    for item in cf[blk].keys(): # make a copy of all the items in the block
4325        dblk[item] = cf[blk][item]
4326    return dblk,loopstructure
4327
4328def dict2CIF(dblk,loopstructure,blockname='Template'):
4329    '''Create a PyCifRW CIF object containing a single CIF
4330    block object from a dict and loop structure list.
4331
4332    :param dblk: a dict containing values for each CIF item
4333    :param list loopstructure: a list of lists containing the contents of
4334      each loop, as an example::
4335
4336         [ ["_a","_b"], ["_c"], ["_d_1","_d_2","_d_3"]]
4337
4338      this describes a CIF with this type of structure::
4339
4340        loop_ _a _b <a1> <b1> <a2> ...
4341        loop_ _c <c1> <c2>...
4342        loop _d_1 _d_2 _d_3 ...
4343
4344      Note that the values for each looped CIF item, such as _a,
4345      are contained in a list, for example as cifblk["_a"]
4346
4347    :param str blockname: an optional name for the CIF block.
4348      Defaults to 'Template'
4349
4350    :returns: the newly created PyCifRW CIF object
4351    '''
4352
4353    import CifFile as cif # PyCifRW from James Hester
4354    # compile a 'list' of items in loops
4355    loopnames = set()
4356    for i in loopstructure:
4357        loopnames |= set(i)
4358    # create a new block
4359    newblk = cif.CifBlock()
4360    # add the looped items
4361    for keys in loopstructure:
4362        vals = []
4363        for key in keys:
4364            vals.append(dblk[key])
4365        newblk.AddCifItem(([keys],[vals]))
4366    # add the non-looped items
4367    for item in dblk:
4368        if item in loopnames: continue
4369        newblk[item] = dblk[item]
4370    # create a CIF and add the block
4371    newcf = cif.CifFile()
4372    newcf[blockname] = newblk
4373    return newcf
4374
4375
4376class EditCIFtemplate(wx.Dialog):
4377    '''Create a dialog for editing a CIF template. The edited information is
4378    placed in cifblk. If the CIF is saved as a file, the name of that file
4379    is saved as ``self.newfile``.
4380
4381    :param wx.Frame parent: parent frame or None
4382    :param cifblk: dict or PyCifRW block containing values for each CIF item
4383    :param list loopstructure: a list of lists containing the contents of
4384      each loop, as an example::
4385
4386         [ ["_a","_b"], ["_c"], ["_d_1","_d_2","_d_3"]]
4387
4388      this describes a CIF with this type of structure::
4389
4390        loop_ _a _b <a1> <b1> <a2> ...
4391        loop_ _c <c1> <c2>...
4392        loop _d_1 _d_2 _d_3 ...
4393
4394      Note that the values for each looped CIF item, such as _a,
4395      are contained in a list, for example as cifblk["_a"]
4396
4397    :param str defaultname: specifies the default file name to be used for
4398      saving the CIF.
4399    '''
4400    def __init__(self,parent,cifblk,loopstructure,defaultname):
4401        OKbuttons = []
4402        self.cifblk = cifblk
4403        self.loopstructure = loopstructure
4404        self.newfile = None
4405        self.defaultname = defaultname
4406        self.G2frame = parent.G2frame
4407        global CIFdic  # once this is loaded, keep it around
4408        if CIFdic is None:
4409            CIFdic = LoadCIFdic()
4410        wx.Dialog.__init__(self,parent,style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
4411
4412        # define widgets that will be needed during panel creation
4413        self.helptxt = wx.StaticText(self,wx.ID_ANY,"")
4414        savebtn = wx.Button(self, wx.ID_CLOSE, "Save as template")
4415        OKbuttons.append(savebtn)
4416        savebtn.Bind(wx.EVT_BUTTON,self._onSave)
4417        OKbtn = wx.Button(self, wx.ID_OK, "Use")
4418        OKbtn.Bind(wx.EVT_BUTTON, lambda x: self.EndModal(wx.ID_OK))
4419        OKbtn.SetDefault()
4420        OKbuttons.append(OKbtn)
4421
4422        self.SetTitle('Edit items in CIF template')
4423        vbox = wx.BoxSizer(wx.VERTICAL)
4424        cpnl = EditCIFpanel(self,cifblk,loopstructure,CIFdic,OKbuttons,size=(300,300))
4425        vbox.Add(cpnl, 1, wx.ALIGN_LEFT|wx.ALL|wx.EXPAND, 0)
4426        G2G.HorizontalLine(vbox,self)
4427        vbox.Add(self.helptxt, 0, wx.EXPAND|wx.ALL, 5)
4428        G2G.HorizontalLine(vbox,self)
4429        btnsizer = wx.BoxSizer(wx.HORIZONTAL)
4430        btn = wx.Button(self, wx.ID_CANCEL)
4431        btnsizer.Add(btn,0,wx.ALIGN_CENTER|wx.ALL)
4432        btnsizer.Add(savebtn,0,wx.ALIGN_CENTER|wx.ALL)
4433        btnsizer.Add(OKbtn,0,wx.ALIGN_CENTER|wx.ALL)
4434        vbox.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5)
4435        self.SetSizer(vbox)
4436        vbox.Fit(self)
4437    def Post(self):
4438        '''Display the dialog
4439
4440        :returns: True unless Cancel has been pressed.
4441        '''
4442        return (self.ShowModal() == wx.ID_OK)
4443    def _onSave(self,event):
4444        'Save CIF entries in a template file'
4445        pth = G2G.GetExportPath(self.G2frame)
4446        dlg = wx.FileDialog(
4447            self, message="Save as CIF template",
4448            defaultDir=pth,
4449            defaultFile=self.defaultname,
4450            wildcard="CIF (*.cif)|*.cif",
4451            style=wx.FD_SAVE)
4452        val = (dlg.ShowModal() == wx.ID_OK)
4453        fil = dlg.GetPath()
4454        dlg.Destroy()
4455        if val: # ignore a Cancel button
4456            fil = os.path.splitext(fil)[0]+'.cif' # force extension
4457            fp = open(fil,'w')
4458            newcf = dict2CIF(self.cifblk,self.loopstructure)
4459            fp.write(newcf.WriteOut())
4460            fp.close()
4461            self.newfile = fil
4462            self.EndModal(wx.ID_OK)
4463
4464class EditCIFpanel(wxscroll.ScrolledPanel):
4465    '''Creates a scrolled panel for editing CIF template items
4466
4467    :param wx.Frame parent: parent frame where panel will be placed
4468    :param cifblk: dict or PyCifRW block containing values for each CIF item
4469    :param list loopstructure: a list of lists containing the contents of
4470      each loop, as an example::
4471
4472         [ ["_a","_b"], ["_c"], ["_d_1","_d_2","_d_3"]]
4473
4474      this describes a CIF with this type of structure::
4475
4476        loop_ _a _b <a1> <b1> <a2> ...
4477        loop_ _c <c1> <c2>...
4478        loop _d_1 _d_2 _d_3 ...
4479
4480      Note that the values for each looped CIF item, such as _a,
4481      are contained in a list, for example as cifblk["_a"]
4482
4483    :param dict cifdic: optional CIF dictionary definitions
4484    :param list OKbuttons: A list of wx.Button objects that should
4485      be disabled when information in the CIF is invalid
4486    :param (other): optional keyword parameters for wx.ScrolledPanel
4487    '''
4488    def __init__(self, parent, cifblk, loopstructure, cifdic={}, OKbuttons=[], **kw):
4489        self.parent = parent
4490        wxscroll.ScrolledPanel.__init__(self, parent, wx.ID_ANY, **kw)
4491        self.vbox = None
4492        self.AddDict = None
4493        self.cifdic = cifdic
4494        self.cifblk = cifblk
4495