source: trunk/exports/G2export_CIF.py @ 5052

Last change on this file since 5052 was 5052, checked in by toby, 2 years ago

Update PSD model in FPA code/docs; minor changes in comments

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