source: trunk/exports/G2export_CIF.py @ 5042

Last change on this file since 5042 was 5042, checked in by toby, 18 months ago

implement sequential CIF export; minor CIF bug fixes; add su to seq res for WtFrac?; Add new sequential project export menu items; move some lattice labels to G2lat; Use G2strIO (not G2strIO) throughout

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Author Revision URL Id
File size: 200.9 KB
Line 
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3########### SVN repository information ###################
4# $Date: 2021-10-04 22:14:24 +0000 (Mon, 04 Oct 2021) $
5# $Author: toby $
6# $Revision: 5042 $
7# $URL: trunk/exports/G2export_CIF.py $
8# $Id: G2export_CIF.py 5042 2021-10-04 22:14:24Z 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: 5042 $")
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.
1846
1847            TODO: need a method to select publication flags for distances/angles
1848            '''
1849            #breakpoint()
1850            Atoms = phasedict['Atoms']
1851            generalData = phasedict['General']
1852            parmDict = seqData[histname]['parmDict']
1853#            sigDict = dict(zip(seqData[hist]['varyList'],seqData[hist]['sig']))
1854            # create a dict for storing Pub flag for bonds/angles, if needed
1855            if phasedict['General'].get("DisAglHideFlag") is None:
1856                phasedict['General']["DisAglHideFlag"] = {}
1857            DisAngSel = phasedict['General']["DisAglHideFlag"]
1858            cx,ct,cs,cia = phasedict['General']['AtomPtrs']
1859            cn = ct-1
1860#            fpfx = str(phasedict['pId'])+'::Afrac:'
1861            cfrac = cx+3
1862            DisAglData = {}
1863            # create a list of atoms, but skip atoms with zero occupancy
1864            xyz = []
1865            fpfx = str(phasedict['pId'])+'::Afrac:'
1866            for i,atom in enumerate(Atoms):
1867                if parmDict.get(fpfx+str(i),atom[cfrac]) == 0.0: continue
1868                thisatom = [i] + atom[cn:cn+2]
1869                for j,lab in enumerate(['x','y','z']):
1870                    xyzkey = str(phasedict['pId'])+'::A'+ lab + ':' +str(i)
1871                    thisatom.append(parmDict.get(xyzkey,atom[cx+j]))
1872                xyz.append(thisatom)
1873            DisAglData['OrigAtoms'] = xyz
1874            DisAglData['TargAtoms'] = xyz
1875            SymOpList,offsetList,symOpList,G2oprList,G2opcodes = G2spc.AllOps(
1876                generalData['SGData'])
1877
1878#            xpandSGdata = generalData['SGData'].copy()
1879#            xpandSGdata.update({'SGOps':symOpList,
1880#                                'SGInv':False,
1881#                                'SGLatt':'P',
1882#                                'SGCen':np.array([[0, 0, 0]]),})
1883#            DisAglData['SGData'] = xpandSGdata
1884            DisAglData['SGData'] = generalData['SGData'].copy()
1885
1886            DisAglData['Cell'] = cellList  #+ volume
1887            if 'pId' in phasedict:
1888                DisAglData['pId'] = phasedict['pId']
1889                DisAglData['covData'] = seqData[histname]
1890                # self.OverallParms['Covariance']
1891            try:
1892                AtomLabels,DistArray,AngArray = G2stMn.RetDistAngle(
1893                    generalData['DisAglCtls'],
1894                    DisAglData)
1895            except KeyError:        # inside DistAngle for missing atom types in DisAglCtls
1896                print(u'**** ERROR computing distances & angles for phase {} ****\nresetting to default values'.format(phasenam))
1897                data = generalData['DisAglCtls'] = {}
1898                data['Name'] = generalData['Name']
1899                data['Factors'] = [0.85,0.85]
1900                data['AtomTypes'] = generalData['AtomTypes']
1901                data['BondRadii'] = generalData['BondRadii'][:]
1902                data['AngleRadii'] = generalData['AngleRadii'][:]
1903                try:
1904                    AtomLabels,DistArray,AngArray = G2stMn.RetDistAngle(
1905                        generalData['DisAglCtls'],
1906                        DisAglData)
1907                except:
1908                    print('Reset failed. To fix this, use the Reset button in the "edit distance/angle menu" for this phase')
1909                    return
1910
1911            # loop over interatomic distances for this phase
1912            WriteCIFitem(self.fp, '\n# MOLECULAR GEOMETRY')
1913            First = True
1914            for i in sorted(AtomLabels.keys()):
1915                Dist = DistArray[i]
1916                for D in Dist:
1917                    line = '  '+PutInCol(AtomLabels[i],6)+PutInCol(AtomLabels[D[0]],6)
1918                    sig = D[4]
1919                    if sig == 0: sig = -0.00009
1920                    line += PutInCol(G2mth.ValEsd(D[3],sig,True),10)
1921                    line += "  1_555 "
1922                    symopNum = G2opcodes.index(D[2])
1923                    line += " {:3d}_".format(symopNum+1)
1924                    for d,o in zip(D[1],offsetList[symopNum]):
1925                        line += "{:1d}".format(d-o+5)
1926                    if DisAngSel.get((i,tuple(D[0:3]))):
1927                        line += " no"
1928                    else:
1929                        line += " yes"
1930                    if First:
1931                        First = False
1932                        WriteCIFitem(self.fp, 'loop_' +
1933                         '\n   _geom_bond_atom_site_label_1' +
1934                         '\n   _geom_bond_atom_site_label_2' +
1935                         '\n   _geom_bond_distance' +
1936                         '\n   _geom_bond_site_symmetry_1' +
1937                         '\n   _geom_bond_site_symmetry_2' +
1938                         '\n   _geom_bond_publ_flag')
1939                    WriteCIFitem(self.fp, line)
1940
1941            # loop over interatomic angles for this phase
1942            First = True
1943            for i in sorted(AtomLabels.keys()):
1944                Dist = DistArray[i]
1945                for k,j,tup in AngArray[i]:
1946                    Dj = Dist[j]
1947                    Dk = Dist[k]
1948                    line = '  '+PutInCol(AtomLabels[Dj[0]],6)+PutInCol(AtomLabels[i],6)+PutInCol(AtomLabels[Dk[0]],6)
1949                    sig = tup[1]
1950                    if sig == 0: sig = -0.009
1951                    line += PutInCol(G2mth.ValEsd(tup[0],sig,True),10)
1952                    line += " {:3d}_".format(G2opcodes.index(Dj[2])+1)
1953                    for d in Dj[1]:
1954                        line += "{:1d}".format(d+5)
1955                    line += "  1_555 "
1956                    line += " {:3d}_".format(G2opcodes.index(Dk[2])+1)
1957                    for d in Dk[1]:
1958                        line += "{:1d}".format(d+5)
1959                    key = (tuple(Dk[0:3]),i,tuple(Dj[0:3]))
1960                    if DisAngSel.get(key):
1961                        line += " no"
1962                    else:
1963                        line += " yes"
1964                    if First:
1965                        First = False
1966                        WriteCIFitem(self.fp, '\nloop_' +
1967                         '\n   _geom_angle_atom_site_label_1' +
1968                         '\n   _geom_angle_atom_site_label_2' +
1969                         '\n   _geom_angle_atom_site_label_3' +
1970                         '\n   _geom_angle' +
1971                         '\n   _geom_angle_site_symmetry_1' +
1972                         '\n   _geom_angle_site_symmetry_2' +
1973                         '\n   _geom_angle_site_symmetry_3' +
1974                         '\n   _geom_angle_publ_flag')
1975                    WriteCIFitem(self.fp, line)
1976
1977        def WriteSeqOverallPhaseInfo(phasenam,histblk):
1978            'Write out the phase information for the selected phase for the overall block in a sequential fit'
1979            WriteCIFitem(self.fp, '# overall phase info for '+str(phasenam) + ' follows')
1980            phasedict = self.Phases[phasenam] # pointer to current phase info
1981            WriteCIFitem(self.fp, '_pd_phase_name', phasenam)
1982
1983            WriteCIFitem(self.fp, '_symmetry_cell_setting',
1984                         phasedict['General']['SGData']['SGSys'])
1985
1986            if phasedict['General']['Type'] in ['nuclear','macromolecular']:
1987                spacegroup = phasedict['General']['SGData']['SpGrp'].strip()
1988                # regularize capitalization and remove trailing H/R
1989                spacegroup = spacegroup[0].upper() + spacegroup[1:].lower().rstrip('rh ')
1990                WriteCIFitem(self.fp, '_symmetry_space_group_name_H-M',spacegroup)
1991   
1992                # generate symmetry operations including centering and center of symmetry
1993                SymOpList,offsetList,symOpList,G2oprList,G2opcodes = G2spc.AllOps(
1994                    phasedict['General']['SGData'])
1995                WriteCIFitem(self.fp, 'loop_\n    _space_group_symop_id\n    _space_group_symop_operation_xyz')
1996                for i,op in enumerate(SymOpList,start=1):
1997                    WriteCIFitem(self.fp, '   {:3d}  {:}'.format(i,op.lower()))
1998            elif phasedict['General']['Type'] == 'magnetic':
1999                parentSpGrp = phasedict['General']['SGData']['SpGrp'].strip()
2000                parentSpGrp = parentSpGrp[0].upper() + parentSpGrp[1:].lower().rstrip('rh ')
2001                WriteCIFitem(self.fp, '_parent_space_group.name_H-M_alt',parentSpGrp)
2002#                [Trans,Uvec,Vvec] = phasedict['General']['SGData']['fromParent']         #save these
2003                spacegroup = phasedict['General']['SGData']['MagSpGrp'].strip()
2004                spacegroup = spacegroup[0].upper() + spacegroup[1:].lower().rstrip('rh ')
2005                WriteCIFitem(self.fp, '_space_group_magn.name_BNS',spacegroup)
2006                WriteCIFitem(self.fp, '_space_group.magn_point_group',phasedict['General']['SGData']['MagPtGp'])
2007
2008                # generate symmetry operations including centering and center of symmetry
2009                SymOpList,offsetList,symOpList,G2oprList,G2opcodes = G2spc.AllOps(
2010                    phasedict['General']['SGData'])
2011                SpnFlp = phasedict['General']['SGData']['SpnFlp']
2012                WriteCIFitem(self.fp, 'loop_\n    _space_group_symop_magn_operation.id\n    _space_group_symop_magn_operation.xyz')
2013                for i,op in enumerate(SymOpList,start=1):
2014                    if SpnFlp[i-1] >0:
2015                        opr = op.lower()+',+1'
2016                    else:
2017                        opr = op.lower()+',-1'
2018                    WriteCIFitem(self.fp, '   {:3d}  {:}'.format(i,opr))
2019                   
2020            lam = None
2021            if 'X' in histblk['Instrument Parameters'][0]['Type'][0]:
2022                for k in ('Lam','Lam1'):
2023                    if k in histblk['Instrument Parameters'][0]:
2024                        lam = histblk['Instrument Parameters'][0][k][0]
2025                        break
2026            keV = None
2027            if lam: keV = 12.397639/lam
2028            # report cell contents                       
2029            WriteComposition(self.fp, self.Phases[phasenam], phasenam, self.parmDict, False, keV)
2030       
2031        def WriteSeqPhaseVals(phasenam,phasedict,pId,histname):
2032            'Write out the phase information for the selected phase'
2033            WriteCIFitem(self.fp, '_pd_phase_name', phasenam)
2034            cellList,cellSig = getCellwStrain(phasedict,self.seqData,pId,histname)
2035            T = self.Histograms[histname]['Sample Parameters']['Temperature']
2036            try:
2037                T = G2mth.ValEsd(T,-1.0)
2038            except:
2039                pass
2040            WriteCIFitem(self.fp, '_symmetry_cell_setting',
2041                         phasedict['General']['SGData']['SGSys'])
2042
2043            if phasedict['General']['Type'] in ['nuclear','macromolecular']:
2044                spacegroup = phasedict['General']['SGData']['SpGrp'].strip()
2045                # regularize capitalization and remove trailing H/R
2046                spacegroup = spacegroup[0].upper() + spacegroup[1:].lower().rstrip('rh ')
2047                WriteCIFitem(self.fp, '_symmetry_space_group_name_H-M',spacegroup)
2048            WriteCIFitem(self.fp,"_cell_measurement_temperature",T)
2049            defsigL = 3*[-0.00001] + 3*[-0.001] + [-0.01] # significance to use when no sigma
2050            prevsig = 0
2051            for lbl,defsig,val,sig in zip(cellNames,defsigL,cellList,cellSig):
2052                if sig:
2053                    txt = G2mth.ValEsd(val,sig)
2054                    prevsig = -sig # use this as the significance for next value
2055                else:
2056                    txt = G2mth.ValEsd(val,min(defsig,prevsig),True)
2057                WriteCIFitem(self.fp, '_cell_'+lbl,txt)
2058
2059            mass = G2mth.getMass(phasedict['General'])
2060            Volume = cellList[6]
2061            density = mass/(0.6022137*Volume)
2062            WriteCIFitem(self.fp, '_exptl_crystal_density_diffrn',
2063                    G2mth.ValEsd(density,-0.001))
2064
2065            # report atom params
2066            if phasedict['General']['Type'] in ['nuclear','macromolecular']:        #this needs macromolecular variant, etc!
2067                WriteSeqAtomsNuclear(self.fp, cellList, phasedict, phasenam, histname, 
2068                                         self.seqData, self.OverallParms['Rigid bodies'])
2069            else:
2070                print("Warning: no export for sequential "+str(phasedict['General']['Type'])+" coordinates implemented")
2071#                raise Exception("no export for "+str(phasedict['General']['Type'])+" coordinates implemented")
2072
2073            if phasedict['General']['Type'] == 'nuclear':
2074                WriteSeqDistances(phasenam,histname,phasedict,cellList,self.seqData)
2075
2076            # N.B. map info probably not possible w/sequential
2077#            if 'Map' in phasedict['General'] and 'minmax' in phasedict['General']['Map']:
2078#                WriteCIFitem(self.fp, '\n# Difference density results')
2079#                MinMax = phasedict['General']['Map']['minmax']
2080#                WriteCIFitem(self.fp, '_refine_diff_density_max',G2mth.ValEsd(MinMax[0],-0.009))
2081#                WriteCIFitem(self.fp, '_refine_diff_density_min',G2mth.ValEsd(MinMax[1],-0.009))
2082               
2083        def WritePhaseInfo(phasenam,quick=True,oneblock=True):
2084            'Write out the phase information for the selected phase'
2085            WriteCIFitem(self.fp, '\n# phase info for '+str(phasenam) + ' follows')
2086            phasedict = self.Phases[phasenam] # pointer to current phase info
2087            WriteCIFitem(self.fp, '_pd_phase_name', phasenam)
2088            cellList,cellSig = self.GetCell(phasenam)
2089            if quick:  # leave temperature as unknown
2090                WriteCIFitem(self.fp,"_cell_measurement_temperature","?")
2091            elif oneblock:
2092                pass # temperature should be written when the histogram saved later
2093            else: # get T set in _SelectPhaseT_CellSelectHist and possibly get new cell params
2094                T,hRanId = self.CellHistSelection.get(phasedict['ranId'],
2095                                                          ('?',None))
2096                try:
2097                    T = G2mth.ValEsd(T,-1.0)
2098                except:
2099                    pass
2100                WriteCIFitem(self.fp,"_cell_measurement_temperature",T)
2101                for h in self.Histograms:
2102                    if self.Histograms[h]['ranId'] == hRanId:
2103                        pId = phasedict['pId']
2104                        hId = self.Histograms[h]['hId']
2105                        cellList,cellSig = G2stIO.getCellSU(pId,hId,
2106                                        phasedict['General']['SGData'],
2107                                        self.parmDict,
2108                                        self.OverallParms['Covariance'])
2109                        break
2110                else:
2111                    T = '?'
2112
2113            defsigL = 3*[-0.00001] + 3*[-0.001] + [-0.01] # significance to use when no sigma
2114            prevsig = 0
2115            for lbl,defsig,val,sig in zip(cellNames,defsigL,cellList,cellSig):
2116                if sig:
2117                    txt = G2mth.ValEsd(val,sig)
2118                    prevsig = -sig # use this as the significance for next value
2119                else:
2120                    txt = G2mth.ValEsd(val,min(defsig,prevsig),True)
2121                WriteCIFitem(self.fp, '_cell_'+lbl,txt)
2122               
2123            density = G2mth.getDensity(phasedict['General'])[0]
2124            WriteCIFitem(self.fp, '_exptl_crystal_density_diffrn',
2125                    G2mth.ValEsd(density,-0.001))                   
2126
2127            WriteCIFitem(self.fp, '_symmetry_cell_setting',
2128                         phasedict['General']['SGData']['SGSys'])
2129
2130            if phasedict['General']['Type'] in ['nuclear','macromolecular']:
2131                spacegroup = phasedict['General']['SGData']['SpGrp'].strip()
2132                # regularize capitalization and remove trailing H/R
2133                spacegroup = spacegroup[0].upper() + spacegroup[1:].lower().rstrip('rh ')
2134                WriteCIFitem(self.fp, '_symmetry_space_group_name_H-M',spacegroup)
2135   
2136                # generate symmetry operations including centering and center of symmetry
2137                SymOpList,offsetList,symOpList,G2oprList,G2opcodes = G2spc.AllOps(
2138                    phasedict['General']['SGData'])
2139                WriteCIFitem(self.fp, 'loop_\n    _space_group_symop_id\n    _space_group_symop_operation_xyz')
2140                for i,op in enumerate(SymOpList,start=1):
2141                    WriteCIFitem(self.fp, '   {:3d}  {:}'.format(i,op.lower()))
2142            elif phasedict['General']['Type'] == 'magnetic':
2143                parentSpGrp = phasedict['General']['SGData']['SpGrp'].strip()
2144                parentSpGrp = parentSpGrp[0].upper() + parentSpGrp[1:].lower().rstrip('rh ')
2145                WriteCIFitem(self.fp, '_parent_space_group.name_H-M_alt',parentSpGrp)
2146#                [Trans,Uvec,Vvec] = phasedict['General']['SGData']['fromParent']         #save these
2147                spacegroup = phasedict['General']['SGData']['MagSpGrp'].strip()
2148                spacegroup = spacegroup[0].upper() + spacegroup[1:].lower().rstrip('rh ')
2149                WriteCIFitem(self.fp, '_space_group_magn.name_BNS',spacegroup)
2150                WriteCIFitem(self.fp, '_space_group.magn_point_group',phasedict['General']['SGData']['MagPtGp'])
2151
2152                # generate symmetry operations including centering and center of symmetry
2153                SymOpList,offsetList,symOpList,G2oprList,G2opcodes = G2spc.AllOps(
2154                    phasedict['General']['SGData'])
2155                SpnFlp = phasedict['General']['SGData']['SpnFlp']
2156                WriteCIFitem(self.fp, 'loop_\n    _space_group_symop_magn_operation.id\n    _space_group_symop_magn_operation.xyz')
2157                for i,op in enumerate(SymOpList,start=1):
2158                    if SpnFlp[i-1] >0:
2159                        opr = op.lower()+',+1'
2160                    else:
2161                        opr = op.lower()+',-1'
2162                    WriteCIFitem(self.fp, '   {:3d}  {:}'.format(i,opr))
2163
2164            # loop over histogram(s) used in this phase
2165            if not oneblock and not self.quickmode:
2166                # report pointers to the histograms used in this phase
2167                histlist = []
2168                for hist in self.Phases[phasenam]['Histograms']:
2169                    if self.Phases[phasenam]['Histograms'][hist]['Use']:
2170                        if phasebyhistDict.get(hist):
2171                            phasebyhistDict[hist].append(phasenam)
2172                        else:
2173                            phasebyhistDict[hist] = [phasenam,]
2174                        blockid = datablockidDict.get(hist)
2175                        if not blockid:
2176                            print("Internal error: no block for data. Phase "+str(
2177                                phasenam)+" histogram "+str(hist))
2178                            histlist = []
2179                            break
2180                        histlist.append(blockid)
2181
2182                if len(histlist) == 0:
2183                    WriteCIFitem(self.fp, '# Note: phase has no associated data')
2184
2185            # report atom params
2186            if phasedict['General']['Type'] in ['nuclear','macromolecular']:        #this needs macromolecular variant, etc!
2187                try:
2188                    self.labellist
2189                except AttributeError:
2190                    self.labellist = []
2191                WriteAtomsNuclear(self.fp, self.Phases[phasenam], phasenam,
2192                                  self.parmDict, self.sigDict, self.labellist,
2193                                      self.OverallParms['Rigid bodies'])
2194            else:
2195                try:
2196                    self.labellist
2197                except AttributeError:
2198                    self.labellist = []
2199                WriteAtomsMagnetic(self.fp, self.Phases[phasenam], phasenam,
2200                                  self.parmDict, self.sigDict, self.labellist)
2201#                raise Exception("no export for "+str(phasedict['General']['Type'])+" coordinates implemented")
2202            keV = None             
2203            if oneblock: # get xray wavelength
2204                lamlist = []
2205                for hist in self.Histograms:
2206                    if 'X' not in self.Histograms[hist]['Instrument Parameters'][0]['Type'][0]:
2207                        continue
2208                    for k in ('Lam','Lam1'):
2209                        if k in self.Histograms[hist]['Instrument Parameters'][0]:
2210                            lamlist.append(self.Histograms[hist]['Instrument Parameters'][0][k][0])
2211                            break
2212                if len(lamlist) == 1:
2213                    keV = 12.397639/lamlist[0]
2214
2215            # report cell contents                       
2216            WriteComposition(self.fp, self.Phases[phasenam], phasenam, self.parmDict, self.quickmode, keV)
2217            if not self.quickmode and phasedict['General']['Type'] == 'nuclear':      # report distances and angles
2218                WriteDistances(phasenam)
2219            if 'Map' in phasedict['General'] and 'minmax' in phasedict['General']['Map']:
2220                WriteCIFitem(self.fp, '\n# Difference density results')
2221                MinMax = phasedict['General']['Map']['minmax']
2222                WriteCIFitem(self.fp, '_refine_diff_density_max',G2mth.ValEsd(MinMax[0],-0.009))
2223                WriteCIFitem(self.fp, '_refine_diff_density_min',G2mth.ValEsd(MinMax[1],-0.009))
2224               
2225        def Yfmt(ndec,val):
2226            'Format intensity values'
2227            try:
2228                out = ("{:."+str(ndec)+"f}").format(val)
2229                out = out.rstrip('0')  # strip zeros to right of decimal
2230                return out.rstrip('.')  # and decimal place when not needed
2231            except TypeError:
2232                print(val)
2233                return '.'
2234           
2235        def WriteReflStat(refcount,hklmin,hklmax,dmin,dmax,nRefSets=1):
2236            'Write reflection statistics'
2237            WriteCIFitem(self.fp, '_reflns_number_total', str(refcount))
2238            if hklmin is not None and nRefSets == 1: # hkl range has no meaning with multiple phases
2239                WriteCIFitem(self.fp, '_reflns_limit_h_min', str(int(hklmin[0])))
2240                WriteCIFitem(self.fp, '_reflns_limit_h_max', str(int(hklmax[0])))
2241                WriteCIFitem(self.fp, '_reflns_limit_k_min', str(int(hklmin[1])))
2242                WriteCIFitem(self.fp, '_reflns_limit_k_max', str(int(hklmax[1])))
2243                WriteCIFitem(self.fp, '_reflns_limit_l_min', str(int(hklmin[2])))
2244                WriteCIFitem(self.fp, '_reflns_limit_l_max', str(int(hklmax[2])))
2245            if hklmin is not None:
2246                WriteCIFitem(self.fp, '_reflns_d_resolution_low  ', G2mth.ValEsd(dmax,-0.009))
2247                WriteCIFitem(self.fp, '_reflns_d_resolution_high ', G2mth.ValEsd(dmin,-0.009))
2248
2249        def WritePowderData(histlbl,seq=False):
2250            'Write out the selected powder diffraction histogram info'
2251            histblk = self.Histograms[histlbl]
2252            inst = histblk['Instrument Parameters'][0]
2253            hId = histblk['hId']
2254            pfx = ':' + str(hId) + ':'
2255
2256            if 'Lam1' in inst:
2257                ratio = self.parmDict.get('I(L2)/I(L1)',inst['I(L2)/I(L1)'][1])
2258                sratio = self.sigDict.get('I(L2)/I(L1)',-0.0009)
2259                lam1 = self.parmDict.get('Lam1',inst['Lam1'][1])
2260                slam1 = self.sigDict.get('Lam1',-0.00009)
2261                lam2 = self.parmDict.get('Lam2',inst['Lam2'][1])
2262                slam2 = self.sigDict.get('Lam2',-0.00009)
2263                # always assume Ka1 & Ka2 if two wavelengths are present
2264                WriteCIFitem(self.fp, '_diffrn_radiation_type','K\\a~1,2~')
2265                WriteCIFitem(self.fp, 'loop_' +
2266                             '\n   _diffrn_radiation_wavelength' +
2267                             '\n   _diffrn_radiation_wavelength_wt' +
2268                             '\n   _diffrn_radiation_wavelength_id')
2269                WriteCIFitem(self.fp, '  ' + PutInCol(G2mth.ValEsd(lam1,slam1),15)+
2270                             PutInCol('1.0',15) +
2271                             PutInCol('1',5))
2272                WriteCIFitem(self.fp, '  ' + PutInCol(G2mth.ValEsd(lam2,slam2),15)+
2273                             PutInCol(G2mth.ValEsd(ratio,sratio),15)+
2274                             PutInCol('2',5))
2275            elif 'Lam' in inst:
2276                lam1 = self.parmDict.get('Lam',inst['Lam'][1])
2277                slam1 = self.sigDict.get('Lam',-0.00009)
2278                WriteCIFitem(self.fp, '_diffrn_radiation_wavelength',G2mth.ValEsd(lam1,slam1))
2279
2280            if not oneblock:
2281                if seq:
2282                    pass
2283                elif not phasebyhistDict.get(histlbl):
2284                    WriteCIFitem(self.fp, '\n# No phases associated with this data set')
2285                else:
2286                    WriteCIFitem(self.fp, '\n# PHASE TABLE')
2287                    WriteCIFitem(self.fp, 'loop_' +
2288                                 '\n   _pd_phase_id' +
2289                                 '\n   _pd_phase_block_id' +
2290                                 '\n   _pd_phase_mass_%')
2291                    wtFrSum = 0.
2292                    for phasenam in phasebyhistDict.get(histlbl):
2293                        hapData = self.Phases[phasenam]['Histograms'][histlbl]
2294                        General = self.Phases[phasenam]['General']
2295                        wtFrSum += hapData['Scale'][0]*General['Mass']
2296
2297                    for phasenam in phasebyhistDict.get(histlbl):
2298                        hapData = self.Phases[phasenam]['Histograms'][histlbl]
2299                        General = self.Phases[phasenam]['General']
2300                        wtFr = hapData['Scale'][0]*General['Mass']/wtFrSum
2301                        pfx = str(self.Phases[phasenam]['pId'])+':'+str(hId)+':'
2302                        if pfx+'Scale' in self.sigDict:
2303                            sig = self.sigDict[pfx+'Scale']*wtFr/hapData['Scale'][0]
2304                        else:
2305                            sig = -0.0001
2306                        WriteCIFitem(self.fp,
2307                            '  '+
2308                            str(self.Phases[phasenam]['pId']) +
2309                            '  '+datablockidDict[phasenam]+
2310                            '  '+G2mth.ValEsd(wtFr,sig)
2311                            )
2312                    WriteCIFitem(self.fp, 'loop_' +
2313                                 '\n   _gsas_proc_phase_R_F_factor' +
2314                                 '\n   _gsas_proc_phase_R_Fsqd_factor' +
2315                                 '\n   _gsas_proc_phase_id' +
2316                                 '\n   _gsas_proc_phase_block_id')
2317                    for phasenam in phasebyhistDict.get(histlbl):
2318                        pfx = str(self.Phases[phasenam]['pId'])+':'+str(hId)+':'
2319                        WriteCIFitem(self.fp,
2320                            '  '+
2321                            '  '+G2mth.ValEsd(histblk[pfx+'Rf']/100.,-.00009) +
2322                            '  '+G2mth.ValEsd(histblk[pfx+'Rf^2']/100.,-.00009)+
2323                            '  '+str(self.Phases[phasenam]['pId'])+
2324                            '  '+datablockidDict[phasenam]
2325                            )
2326            elif len(self.Phases) == 1:
2327                # single phase in this histogram
2328                # get the phase number here
2329                pId = self.Phases[list(self.Phases.keys())[0]]['pId']
2330                pfx = str(pId)+':'+str(hId)+':'
2331                WriteCIFitem(self.fp, '_refine_ls_R_F_factor      ','%.5f'%(histblk[pfx+'Rf']/100.))
2332                WriteCIFitem(self.fp, '_refine_ls_R_Fsqd_factor   ','%.5f'%(histblk[pfx+'Rf^2']/100.))
2333               
2334            try:
2335                WriteCIFitem(self.fp, '_pd_proc_ls_prof_R_factor   ','%.5f'%(histblk['R']/100.))
2336                WriteCIFitem(self.fp, '_pd_proc_ls_prof_wR_factor  ','%.5f'%(histblk['wR']/100.))
2337                WriteCIFitem(self.fp, '_gsas_proc_ls_prof_R_B_factor ','%.5f'%(histblk['Rb']/100.))
2338                WriteCIFitem(self.fp, '_gsas_proc_ls_prof_wR_B_factor','%.5f'%(histblk['wRb']/100.))
2339                WriteCIFitem(self.fp, '_pd_proc_ls_prof_wR_expected','%.5f'%(histblk['wRmin']/100.))
2340            except KeyError:
2341                pass
2342
2343            if histblk['Instrument Parameters'][0]['Type'][1][1] == 'X':
2344                WriteCIFitem(self.fp, '_diffrn_radiation_probe','x-ray')
2345                pola = histblk['Instrument Parameters'][0].get('Polariz.')
2346                if pola:
2347                    pfx = ':' + str(hId) + ':'
2348                    sig = self.sigDict.get(pfx+'Polariz.',-0.0009)
2349                    txt = G2mth.ValEsd(pola[1],sig)
2350                    WriteCIFitem(self.fp, '_diffrn_radiation_polarisn_ratio',txt)
2351            elif histblk['Instrument Parameters'][0]['Type'][1][1] == 'N':
2352                WriteCIFitem(self.fp, '_diffrn_radiation_probe','neutron')
2353            if 'T' in inst['Type'][0]:
2354                txt = G2mth.ValEsd(inst['2-theta'][0],-0.009)
2355                WriteCIFitem(self.fp, '_pd_meas_2theta_fixed',txt)
2356
2357            WriteCIFitem(self.fp, '_pd_proc_ls_background_function',FormatBackground(histblk['Background'],histblk['hId']))
2358
2359            # TODO: this will need help from Bob
2360            #WriteCIFitem(self.fp, '_exptl_absorpt_process_details','?')
2361            #WriteCIFitem(self.fp, '_exptl_absorpt_correction_T_min','?')
2362            #WriteCIFitem(self.fp, '_exptl_absorpt_correction_T_max','?')
2363            #C extinction
2364            #WRITE(IUCIF,'(A)') '# Extinction correction'
2365            #CALL WRVAL(IUCIF,'_gsas_exptl_extinct_corr_T_min',TEXT(1:10))
2366            #CALL WRVAL(IUCIF,'_gsas_exptl_extinct_corr_T_max',TEXT(11:20))
2367
2368            # code removed because it is causing duplication in histogram block 1/26/19 BHT
2369            #if not oneblock:                 # instrumental profile terms go here
2370            #    WriteCIFitem(self.fp, '_pd_proc_ls_profile_function',
2371            #        FormatInstProfile(histblk["Instrument Parameters"],histblk['hId']))
2372
2373            #refprx = '_refln.' # mm
2374            refprx = '_refln_' # normal
2375            # data collection parameters for the powder dataset
2376
2377            temperature = histblk['Sample Parameters'].get('Temperature') # G2 uses K
2378            if not temperature:
2379                T = '?'
2380            else:
2381                T = G2mth.ValEsd(temperature,-0.009,True) # CIF uses K
2382            WriteCIFitem(self.fp, '_diffrn_ambient_temperature',T)
2383
2384            pressure = histblk['Sample Parameters'].get('Pressure') #G2 uses mega-Pascal
2385            if not pressure:
2386                P = '?'
2387            else:
2388                P = G2mth.ValEsd(pressure*1000,-0.09,True) # CIF uses kilopascal (G2 Mpa)
2389            WriteCIFitem(self.fp, '_diffrn_ambient_pressure',P)
2390
2391            WriteCIFitem(self.fp, '\n# STRUCTURE FACTOR TABLE')
2392            # compute maximum intensity reflection
2393            Imax = 0
2394            phaselist = []
2395            for phasenam in histblk['Reflection Lists']:
2396                try:
2397                    scale = self.Phases[phasenam]['Histograms'][histlbl]['Scale'][0]
2398                except KeyError: # reflection table from removed phase?
2399                    continue
2400                phaselist.append(phasenam)
2401                refList = np.asarray(histblk['Reflection Lists'][phasenam]['RefList'])
2402                I100 = scale*refList.T[8]*refList.T[11]
2403                #Icorr = np.array([refl[13] for refl in histblk['Reflection Lists'][phasenam]])[0]
2404                #FO2 = np.array([refl[8] for refl in histblk['Reflection Lists'][phasenam]])
2405                #I100 = scale*FO2*Icorr
2406                Imax = max(Imax,max(I100))
2407
2408            WriteCIFitem(self.fp, 'loop_')
2409            if len(phaselist) > 1:
2410                WriteCIFitem(self.fp, '   _pd_refln_phase_id')
2411            WriteCIFitem(self.fp, '   ' + refprx + 'index_h' +
2412                         '\n   ' + refprx + 'index_k' +
2413                         '\n   ' + refprx + 'index_l' +
2414                         '\n   ' + refprx + 'F_squared_meas' +
2415                         '\n   ' + refprx + 'F_squared_calc' +
2416                         '\n   ' + refprx + 'phase_calc' +
2417                         '\n   _refln_d_spacing')
2418            if Imax > 0:
2419                WriteCIFitem(self.fp, '   _gsas_i100_meas')
2420
2421            refcount = 0
2422            hklmin = None
2423            hklmax = None
2424            dmax = None
2425            dmin = None
2426            for phasenam in phaselist:
2427                scale = self.Phases[phasenam]['Histograms'][histlbl]['Scale'][0]
2428                phaseid = self.Phases[phasenam]['pId']
2429                refcount += len(histblk['Reflection Lists'][phasenam]['RefList'])
2430                refList = np.asarray(histblk['Reflection Lists'][phasenam]['RefList'])
2431                I100 = scale*refList.T[8]*refList.T[11]
2432                for j,ref in enumerate(histblk['Reflection Lists'][phasenam]['RefList']):
2433                    if DEBUG:
2434                        print('DEBUG: skipping reflection list')
2435                        break
2436                    if hklmin is None:
2437                        hklmin = copy.copy(ref[0:3])
2438                        hklmax = copy.copy(ref[0:3])
2439                    if dmin is None:
2440                         dmax = dmin = ref[4]
2441                    if len(phaselist) > 1:
2442                        s = PutInCol(phaseid,2)
2443                    else:
2444                        s = ""
2445                    for i,hkl in enumerate(ref[0:3]):
2446                        hklmax[i] = max(hkl,hklmax[i])
2447                        hklmin[i] = min(hkl,hklmin[i])
2448                        s += PutInCol(int(hkl),4)
2449                    for I in ref[8:10]:
2450                        s += PutInCol(G2mth.ValEsd(I,-0.0009),10)
2451                    s += PutInCol(G2mth.ValEsd(ref[10],-0.9),7)
2452                    dmax = max(dmax,ref[4])
2453                    dmin = min(dmin,ref[4])
2454                    s += PutInCol(G2mth.ValEsd(ref[4],-0.00009),8)
2455                    if Imax > 0:
2456                        s += PutInCol(G2mth.ValEsd(100.*I100[j]/Imax,-0.09),6)
2457                    WriteCIFitem(self.fp, "  "+s)
2458
2459            WriteReflStat(refcount,hklmin,hklmax,dmin,dmax,len(phaselist))
2460            WriteCIFitem(self.fp, '\n# POWDER DATA TABLE')
2461            # is data fixed step? If the step varies by <0.01% treat as fixed step
2462            steps = abs(histblk['Data'][0][1:] - histblk['Data'][0][:-1])
2463            if (max(steps)-min(steps)) > np.mean(steps)/10000.:
2464                fixedstep = False
2465            else:
2466                fixedstep = True
2467
2468            zero = None
2469            if fixedstep and 'T' not in inst['Type'][0]: # and not TOF
2470                WriteCIFitem(self.fp, '_pd_meas_2theta_range_min', G2mth.ValEsd(histblk['Data'][0][0],-0.00009))
2471                WriteCIFitem(self.fp, '_pd_meas_2theta_range_max', G2mth.ValEsd(histblk['Data'][0][-1],-0.00009))
2472                WriteCIFitem(self.fp, '_pd_meas_2theta_range_inc', G2mth.ValEsd(np.mean(steps),-0.00009))
2473                # zero correct, if defined
2474                zerolst = histblk['Instrument Parameters'][0].get('Zero')
2475                if zerolst: zero = zerolst[1]
2476                zero = self.parmDict.get('Zero',zero)
2477                if zero:
2478                    WriteCIFitem(self.fp, '_pd_proc_2theta_range_min', G2mth.ValEsd(histblk['Data'][0][0]-zero,-0.00009))
2479                    WriteCIFitem(self.fp, '_pd_proc_2theta_range_max', G2mth.ValEsd(histblk['Data'][0][-1]-zero,-0.00009))
2480                    WriteCIFitem(self.fp, '_pd_proc_2theta_range_inc', G2mth.ValEsd(steps.sum()/len(steps),-0.00009))
2481
2482            if zero:
2483                WriteCIFitem(self.fp, '_pd_proc_number_of_points', str(len(histblk['Data'][0])))
2484            else:
2485                WriteCIFitem(self.fp, '_pd_meas_number_of_points', str(len(histblk['Data'][0])))
2486            WriteCIFitem(self.fp, '\nloop_')
2487            #            WriteCIFitem(self.fp, '   _pd_proc_d_spacing') # need easy way to get this
2488            if not fixedstep:
2489                if zero:
2490                    WriteCIFitem(self.fp, '   _pd_proc_2theta_corrected')
2491                elif 'T' in inst['Type'][0]: # and not TOF
2492                    WriteCIFitem(self.fp, '   _pd_meas_time_of_flight')
2493                else:
2494                    WriteCIFitem(self.fp, '   _pd_meas_2theta_scan')
2495            # at least for now, always report weights.
2496            #if countsdata:
2497            #    WriteCIFitem(self.fp, '   _pd_meas_counts_total')
2498            #else:
2499            WriteCIFitem(self.fp, '   _pd_meas_intensity_total')
2500            WriteCIFitem(self.fp, '   _pd_calc_intensity_total')
2501            WriteCIFitem(self.fp, '   _pd_proc_intensity_bkg_calc')
2502            WriteCIFitem(self.fp, '   _pd_proc_ls_weight')
2503            maxY = max(histblk['Data'][1].max(),histblk['Data'][3].max())
2504            if maxY < 0: maxY *= -10 # this should never happen, but...
2505            ndec = max(0,10-int(np.log10(maxY))-1) # 10 sig figs should be enough
2506            maxSU = histblk['Data'][2].max()
2507            if maxSU < 0: maxSU *= -1 # this should never happen, but...
2508            ndecSU = max(0,8-int(np.log10(maxSU))-1) # 8 sig figs should be enough
2509            lowlim,highlim = histblk['Limits'][1]
2510
2511            if DEBUG:
2512                print('DEBUG: skipping profile list')
2513            else:
2514                for x,yobs,yw,ycalc,ybkg in zip(histblk['Data'][0].data,        #get the data from these masked arrays
2515                                                histblk['Data'][1].data,
2516                                                histblk['Data'][2].data,
2517                                                histblk['Data'][3].data,
2518                                                histblk['Data'][4].data):
2519                    if lowlim <= x <= highlim:
2520                        pass
2521                    else:
2522                        yw = 0.0 # show the point is not in use
2523
2524                    if fixedstep:
2525                        s = ""
2526                    elif zero:
2527                        s = PutInCol(G2mth.ValEsd(x-zero,-0.00009),10)
2528                    else:
2529                        s = PutInCol(G2mth.ValEsd(x,-0.00009),10)
2530                    s += PutInCol(Yfmt(ndec,yobs),12)
2531                    s += PutInCol(Yfmt(ndec,ycalc),12)
2532                    s += PutInCol(Yfmt(ndec,ybkg),11)
2533                    s += PutInCol(Yfmt(ndecSU,yw),9)
2534                    WriteCIFitem(self.fp, "  "+s)
2535
2536        def WriteSingleXtalData(histlbl):
2537            'Write out the selected single crystal histogram info'
2538            histblk = self.Histograms[histlbl]
2539
2540            #refprx = '_refln.' # mm
2541            refprx = '_refln_' # normal
2542
2543            WriteCIFitem(self.fp, '\n# STRUCTURE FACTOR TABLE')
2544            WriteCIFitem(self.fp, 'loop_' +
2545                         '\n   ' + refprx + 'index_h' +
2546                         '\n   ' + refprx + 'index_k' +
2547                         '\n   ' + refprx + 'index_l' +
2548                         '\n   ' + refprx + 'F_squared_meas' +
2549                         '\n   ' + refprx + 'F_squared_sigma' +
2550                         '\n   ' + refprx + 'F_squared_calc' +
2551                         '\n   ' + refprx + 'phase_calc'
2552                         )
2553
2554            hklmin = None
2555            hklmax = None
2556            dmax = None
2557            dmin = None
2558            refcount = len(histblk['Data']['RefList'])
2559            for ref in histblk['Data']['RefList']:
2560                if ref[3] <= 0:      #skip user rejected reflections (mul <= 0)
2561                    continue
2562                s = "  "
2563                if hklmin is None:
2564                    hklmin = copy.copy(ref[0:3])
2565                    hklmax = copy.copy(ref[0:3])
2566                    dmax = dmin = ref[4]
2567                for i,hkl in enumerate(ref[0:3]):
2568                    hklmax[i] = max(hkl,hklmax[i])
2569                    hklmin[i] = min(hkl,hklmin[i])
2570                    s += PutInCol(int(hkl),4)
2571                if ref[5] == 0.0:
2572                    s += PutInCol(G2mth.ValEsd(ref[8],0),12)
2573                    s += PutInCol('.',10)
2574                    s += PutInCol(G2mth.ValEsd(ref[9],0),12)
2575                else:
2576                    sig = ref[6] * ref[8] / ref[5]
2577                    s += PutInCol(G2mth.ValEsd(ref[8],-abs(sig/10)),12)
2578                    s += PutInCol(G2mth.ValEsd(sig,-abs(sig)/10.),10)
2579                    s += PutInCol(G2mth.ValEsd(ref[9],-abs(sig/10)),12)
2580                s += PutInCol(G2mth.ValEsd(ref[10],-0.9),7)
2581                dmax = max(dmax,ref[4])
2582                dmin = min(dmin,ref[4])
2583                WriteCIFitem(self.fp, s)
2584            if not self.quickmode: # statistics only in a full CIF
2585                WriteReflStat(refcount,hklmin,hklmax,dmin,dmax)
2586                hId = histblk['hId']
2587                hfx = '0:'+str(hId)+':'
2588                phfx = '%d:%d:'%(0,hId)
2589                extType,extModel,extParms = self.Phases[phasenam]['Histograms'][histlbl]['Extinction']
2590                if extModel != 'None':
2591                    WriteCIFitem(self.fp, '# Extinction scaled by 1.e5')
2592                    WriteCIFitem(self.fp, '_refine_ls_extinction_method','Becker-Coppens %s %s'%(extModel,extType))
2593                    sig = -1.e-3
2594                    if extModel == 'Primary':
2595                        parm = extParms['Ep'][0]*1.e5
2596                        if extParms['Ep'][1]:
2597                            sig = self.sigDict[phfx+'Ep']*1.e5
2598                        text = G2mth.ValEsd(parm,sig)
2599                    elif extModel == 'Secondary Type I':
2600                        parm = extParms['Eg'][0]*1.e5
2601                        if extParms['Eg'][1]:
2602                            sig = self.sigDict[phfx+'Eg']*1.e5
2603                        text = G2mth.ValEsd(parm,sig)
2604                    elif extModel == 'Secondary Type II':
2605                        parm = extParms['Es'][0]*1.e5
2606                        if extParms['Es'][1]:
2607                            sig = self.sigDict[phfx+'Es']*1.e5
2608                        text = G2mth.ValEsd(parm,sig)
2609                    elif extModel == 'Secondary Type I & II':
2610                        parm = extParms['Eg'][0]*1.e5
2611                        if extParms['Es'][1]:
2612                            sig = self.sigDict[phfx+'Es']*1.e5
2613                        text = G2mth.ValEsd(parm,sig)
2614                        sig = -1.0e-3
2615                        parm = extParms['Es'][0]*1.e5
2616                        if extParms['Es'][1]:
2617                            sig = self.sigDict[phfx+'Es']*1.e5
2618                        text += G2mth.ValEsd(parm,sig)
2619                    WriteCIFitem(self.fp, '_refine_ls_extinction_coef',text)
2620                    WriteCIFitem(self.fp, '_refine_ls_extinction_expression','Becker & Coppens (1974). Acta Cryst. A30, 129-147')
2621
2622                WriteCIFitem(self.fp, '_refine_ls_wR_factor_gt    ','%.4f'%(histblk['wR']/100.))
2623                WriteCIFitem(self.fp, '_refine_ls_R_factor_gt     ','%.4f'%(histblk[hfx+'Rf']/100.))
2624                WriteCIFitem(self.fp, '_refine_ls_R_Fsqd_factor   ','%.4f'%(histblk[hfx+'Rf^2']/100.))
2625        def EditAuthor(event=None):
2626            'dialog to edit the CIF author info'
2627            'Edit the CIF author name'
2628            dlg = G2G.SingleStringDialog(self.G2frame,
2629                                          'Get CIF Author',
2630                                          'Provide CIF Author name (Last, First)',
2631                                          value=self.author)
2632            if not dlg.Show():
2633                dlg.Destroy()
2634                return False  # cancel was pressed
2635            self.author = dlg.GetValue()
2636            self.shortauthorname = self.author.replace(',','').replace(' ','')[:20]
2637            dlg.Destroy()
2638            try:
2639                self.OverallParms['Controls']["Author"] = self.author # save for future
2640            except KeyError:
2641                pass
2642            return True
2643
2644        def EditInstNames(event=None):
2645            'Provide a dialog for editing instrument names; for sequential fit, only need one name'
2646            dictlist = []
2647            keylist = []
2648            lbllist = []
2649            for hist in sorted(self.Histograms):
2650                if hist.startswith("PWDR"):
2651                    key2 = "Sample Parameters"
2652                    d = self.Histograms[hist][key2]
2653                elif hist.startswith("HKLF"):
2654                    key2 = "Instrument Parameters"
2655                    d = self.Histograms[hist][key2][0]
2656
2657                lbllist.append(hist)
2658                dictlist.append(d)
2659                keylist.append('InstrName')
2660                instrname = d.get('InstrName')
2661                if instrname is None:
2662                    d['InstrName'] = ''
2663                if hist.startswith("PWDR") and seqmode: break               
2664            return G2G.CallScrolledMultiEditor(
2665                self.G2frame,dictlist,keylist,
2666                prelbl=range(1,len(dictlist)+1),
2667                postlbl=lbllist,
2668                title='Instrument names',
2669                header="Edit instrument names. Note that a non-blank\nname is required for all histograms",
2670                CopyButton=True,ASCIIonly=True)
2671
2672        def EditRanges(event):
2673            '''Edit the bond distance/angle search range; phase is determined from
2674            a pointer placed in the button object (.phasedict) that references the
2675            phase dictionary
2676            '''
2677            but = event.GetEventObject()
2678            phasedict = but.phasedict
2679            dlg = G2G.DisAglDialog(
2680                self.G2frame,
2681                phasedict['General']['DisAglCtls'], # edited
2682                phasedict['General'], # defaults
2683                )
2684            if dlg.ShowModal() == wx.ID_OK:
2685                phasedict['General']['DisAglCtls'] = dlg.GetData()
2686            dlg.Destroy()
2687           
2688        def SetCellT(event):
2689            '''Set the temperature value by selection of a histogram
2690            '''
2691            but = event.GetEventObject()
2692            phasenam = but.phase
2693            rId =  self.Phases[phasenam]['ranId']
2694            self.CellHistSelection[rId] = self._CellSelectHist(phasenam)
2695           
2696        def EditCIFDefaults():
2697            '''Fills the CIF Defaults window with controls for editing various CIF export
2698            parameters (mostly related to templates).
2699            '''
2700            self.cifdefs.DestroyChildren()
2701            self.cifdefs.SetTitle('Edit CIF settings')
2702            vbox = wx.BoxSizer(wx.VERTICAL)
2703            vbox.Add(wx.StaticText(self.cifdefs, wx.ID_ANY,'Creating file '+self.filename))
2704            but = wx.Button(self.cifdefs, wx.ID_ANY,'Edit CIF Author')
2705            but.Bind(wx.EVT_BUTTON,EditAuthor)
2706            vbox.Add(but,0,wx.ALIGN_CENTER,3)
2707            but = wx.Button(self.cifdefs, wx.ID_ANY,'Edit Instrument Name(s)')
2708            but.Bind(wx.EVT_BUTTON,EditInstNames)
2709            vbox.Add(but,0,wx.ALIGN_CENTER,3)
2710            cpnl = wxscroll.ScrolledPanel(self.cifdefs,size=(300,300))
2711            cbox = wx.BoxSizer(wx.VERTICAL)
2712            G2G.HorizontalLine(cbox,cpnl)
2713            cbox.Add(
2714                CIFtemplateSelect(self.cifdefs,
2715                                  cpnl,'publ',self.OverallParms['Controls'],
2716                                  EditCIFDefaults,
2717                                  "Publication (overall) template",
2718                                  ),
2719                0,wx.EXPAND|wx.ALIGN_LEFT|wx.ALL)
2720            for phasenam in sorted(self.Phases.keys()):
2721                G2G.HorizontalLine(cbox,cpnl)
2722                title = 'Phase '+phasenam
2723                phasedict = self.Phases[phasenam] # pointer to current phase info
2724                cbox.Add(
2725                    CIFtemplateSelect(self.cifdefs,
2726                                      cpnl,'phase',phasedict['General'],
2727                                      EditCIFDefaults,
2728                                      title,
2729                                      phasenam),
2730                    0,wx.EXPAND|wx.ALIGN_LEFT|wx.ALL)
2731                cpnl.SetSizer(cbox)
2732                if phasedict['General']['Type'] == 'nuclear':
2733                    but = wx.Button(cpnl, wx.ID_ANY,'Edit distance/angle ranges')
2734                    cbox.Add(but,0,wx.ALIGN_LEFT,0)
2735                    cbox.Add((-1,2))
2736                    but.phasedict = self.Phases[phasenam]  # set a pointer to current phase info
2737                    but.Bind(wx.EVT_BUTTON,EditRanges)     # phase bond/angle ranges
2738                    but = wx.Button(cpnl, wx.ID_ANY,'Set distance/angle publication flags')
2739                    but.phase = phasenam  # set a pointer to current phase info
2740                    but.Bind(wx.EVT_BUTTON,SelectDisAglFlags)     # phase bond/angle ranges
2741                    cbox.Add(but,0,wx.ALIGN_LEFT,0)
2742                if self._CellSelectNeeded(phasenam):
2743                    but = wx.Button(cpnl, wx.ID_ANY,'Select cell temperature')
2744                    cbox.Add(but,0,wx.ALIGN_LEFT,0)
2745                    cbox.Add((-1,2))
2746                    but.phase = phasenam  # set a pointer to current phase info
2747                    but.Bind(wx.EVT_BUTTON,SetCellT)
2748                cbox.Add((-1,2))
2749            for i in sorted(self.powderDict.keys()):
2750                G2G.HorizontalLine(cbox,cpnl)
2751                if seqmode:
2752                    hist = self.powderDict[i]
2753                    histblk = self.Histograms[hist]
2754                    title = 'All Powder datasets'
2755                    self.seqSmplParms = {}
2756                    cbox.Add(
2757                        CIFtemplateSelect(self.cifdefs,
2758                                      cpnl,'powder',self.seqSmplParms,
2759                                      EditCIFDefaults,
2760                                      title,
2761                                      histblk["Sample Parameters"]['InstrName']),
2762                        0,wx.EXPAND|wx.ALIGN_LEFT|wx.ALL)
2763                    break
2764                hist = self.powderDict[i]
2765                histblk = self.Histograms[hist]
2766                title = 'Powder dataset '+hist[5:]
2767                cbox.Add(
2768                    CIFtemplateSelect(self.cifdefs,
2769                                      cpnl,'powder',histblk["Sample Parameters"],
2770                                      EditCIFDefaults,
2771                                      title,
2772                                      histblk["Sample Parameters"]['InstrName']),
2773                    0,wx.EXPAND|wx.ALIGN_LEFT|wx.ALL)
2774            for i in sorted(self.xtalDict.keys()):
2775                G2G.HorizontalLine(cbox,cpnl)
2776                hist = self.xtalDict[i]
2777                histblk = self.Histograms[hist]
2778                title = 'Single Xtal dataset '+hist[5:]
2779                cbox.Add(
2780                    CIFtemplateSelect(self.cifdefs,
2781                                      cpnl,'single',histblk["Instrument Parameters"][0],
2782                                      EditCIFDefaults,
2783                                      title,
2784                                      histblk["Instrument Parameters"][0]['InstrName']),
2785                    0,wx.EXPAND|wx.ALIGN_LEFT|wx.ALL)
2786            cpnl.SetSizer(cbox)
2787            cpnl.SetAutoLayout(1)
2788            cpnl.SetupScrolling()
2789            #cpnl.Bind(rw.EVT_RW_LAYOUT_NEEDED, self.OnLayoutNeeded) # needed if sizes change
2790            cpnl.Layout()
2791
2792            vbox.Add(cpnl, 1, wx.ALIGN_LEFT|wx.ALL|wx.EXPAND, 0)
2793            btnsizer = wx.StdDialogButtonSizer()
2794            btn = wx.Button(self.cifdefs, wx.ID_OK, "Create CIF")
2795            btn.SetDefault()
2796            btnsizer.AddButton(btn)
2797            btn = wx.Button(self.cifdefs, wx.ID_CANCEL)
2798            btnsizer.AddButton(btn)
2799            btnsizer.Realize()
2800            vbox.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5)
2801            self.cifdefs.SetSizer(vbox)
2802            vbox.Fit(self.cifdefs)
2803            self.cifdefs.Layout()
2804
2805        def OnToggleButton(event):
2806            'Respond to press of ToggleButton in SelectDisAglFlags'
2807            but = event.GetEventObject()
2808            if but.GetValue():
2809                but.DisAglSel[but.key] = True
2810            else:
2811                try:
2812                    del but.DisAglSel[but.key]
2813                except KeyError:
2814                    pass
2815        def keepTrue(event):
2816            event.GetEventObject().SetValue(True)
2817        def keepFalse(event):
2818            event.GetEventObject().SetValue(False)
2819
2820        def SelectDisAglFlags(event):
2821            'Select Distance/Angle use flags for the selected phase'
2822            phasenam = event.GetEventObject().phase
2823            phasedict = self.Phases[phasenam]
2824            SymOpList,offsetList,symOpList,G2oprList,G2opcodes = G2spc.AllOps(phasedict['General']['SGData'])
2825            generalData = phasedict['General']
2826            # create a dict for storing Pub flag for bonds/angles, if needed
2827            if phasedict['General'].get("DisAglHideFlag") is None:
2828                phasedict['General']["DisAglHideFlag"] = {}
2829            DisAngSel = phasedict['General']["DisAglHideFlag"]
2830
2831            cx,ct,cs,cia = phasedict['General']['AtomPtrs']
2832            cn = ct-1
2833            cfrac = cx+3
2834            DisAglData = {}
2835            # create a list of atoms, but skip atoms with zero occupancy
2836            xyz = []
2837            fpfx = str(phasedict['pId'])+'::Afrac:'
2838            for i,atom in enumerate(phasedict['Atoms']):
2839                if self.parmDict.get(fpfx+str(i),atom[cfrac]) == 0.0: continue
2840                xyz.append([i,]+atom[cn:cn+2]+atom[cx:cx+3])
2841            if 'DisAglCtls' not in generalData:
2842                # should not be used, since DisAglDialog should be called
2843                # for all phases before getting here
2844                dlg = G2G.DisAglDialog(
2845                    self.cifdefs,
2846                    {},
2847                    generalData)
2848                if dlg.ShowModal() == wx.ID_OK:
2849                    generalData['DisAglCtls'] = dlg.GetData()
2850                else:
2851                    dlg.Destroy()
2852                    return
2853                dlg.Destroy()
2854            dlg = wx.Dialog(
2855                self.G2frame,
2856                style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
2857            vbox = wx.BoxSizer(wx.VERTICAL)
2858            txt = wx.StaticText(dlg,wx.ID_ANY,'Searching distances for phase '+phasenam
2859                                +'\nPlease wait...')
2860            vbox.Add(txt,0,wx.ALL|wx.EXPAND)
2861            dlg.SetSizer(vbox)
2862            dlg.CenterOnParent()
2863            dlg.Show() # post "please wait"
2864            wx.BeginBusyCursor() # and change cursor
2865
2866            DisAglData['OrigAtoms'] = xyz
2867            DisAglData['TargAtoms'] = xyz
2868            SymOpList,offsetList,symOpList,G2oprList,G2opcodes = G2spc.AllOps(
2869                generalData['SGData'])
2870
2871#            xpandSGdata = generalData['SGData'].copy()
2872#            xpandSGdata.update({'SGOps':symOpList,
2873#                                'SGInv':False,
2874#                                'SGLatt':'P',
2875#                                'SGCen':np.array([[0, 0, 0]]),})
2876#            DisAglData['SGData'] = xpandSGdata
2877            DisAglData['SGData'] = generalData['SGData'].copy()
2878
2879            DisAglData['Cell'] = generalData['Cell'][1:] #+ volume
2880            if 'pId' in phasedict:
2881                DisAglData['pId'] = phasedict['pId']
2882                DisAglData['covData'] = self.OverallParms['Covariance']
2883            try:
2884                AtomLabels,DistArray,AngArray = G2stMn.RetDistAngle(
2885                    generalData['DisAglCtls'],
2886                    DisAglData)
2887            except KeyError:        # inside DistAngle for missing atom types in DisAglCtls
2888                print('**** ERROR - try again but do "Reset" to fill in missing atom types ****')
2889            wx.EndBusyCursor()
2890            txt.SetLabel('Set publication flags for distances and angles in\nphase '+phasenam)
2891            vbox.Add((5,5))
2892            vbox.Add(wx.StaticText(dlg,wx.ID_ANY,
2893                                   'The default is to flag all distances and angles as to be'+
2894                                   '\npublished. Change this by pressing appropriate buttons.'),
2895                     0,wx.ALL|wx.EXPAND)
2896            hbox = wx.BoxSizer(wx.HORIZONTAL)
2897            vbox.Add(hbox)
2898            hbox.Add(wx.StaticText(dlg,wx.ID_ANY,'Button appearance: '))
2899            but = wx.ToggleButton(dlg,wx.ID_ANY,'Publish')
2900            but.Bind(wx.EVT_TOGGLEBUTTON,keepFalse)
2901            hbox.Add(but)
2902            but = wx.ToggleButton(dlg,wx.ID_ANY,"Don't publish")
2903            but.Bind(wx.EVT_TOGGLEBUTTON,keepTrue)
2904            hbox.Add(but)
2905            but.SetValue(True)
2906            G2G.HorizontalLine(vbox,dlg)
2907
2908            cpnl = wxscroll.ScrolledPanel(dlg,size=(400,300))
2909            cbox = wx.BoxSizer(wx.VERTICAL)
2910            for c in sorted(DistArray):
2911                karr = []
2912                UsedCols = {}
2913                cbox.Add(wx.StaticText(cpnl,wx.ID_ANY,
2914                                   'distances to/angles around atom '+AtomLabels[c]))
2915                #dbox = wx.GridBagSizer(hgap=5)
2916                dbox = wx.GridBagSizer()
2917                for i,D in enumerate(DistArray[c]):
2918                    karr.append(tuple(D[0:3]))
2919                    val = "{:.2f}".format(D[3])
2920                    sym = " [{:d} {:d} {:d}]".format(*D[1]) + " #{:d}".format(D[2])
2921                    dbox.Add(wx.StaticText(cpnl,wx.ID_ANY,AtomLabels[D[0]]),
2922                             (i+1,0)
2923                             )
2924                    dbox.Add(wx.StaticText(cpnl,wx.ID_ANY,sym),
2925                             (i+1,1)
2926                             )
2927                    but = wx.ToggleButton(cpnl,wx.ID_ANY,val)
2928                    but.key = (c,karr[-1])
2929                    but.DisAglSel = DisAngSel
2930                    if DisAngSel.get(but.key): but.SetValue(True)
2931                    but.Bind(wx.EVT_TOGGLEBUTTON,OnToggleButton)
2932                    dbox.Add(but,(i+1,2),border=1)
2933                for i,D in enumerate(AngArray[c]):
2934                    val = "{:.1f}".format(D[2][0])
2935                    but = wx.ToggleButton(cpnl,wx.ID_ANY,val)
2936                    but.key = (karr[D[0]],c,karr[D[1]])
2937                    but.DisAglSel = DisAngSel
2938                    if DisAngSel.get(but.key): but.SetValue(True)
2939                    but.Bind(wx.EVT_TOGGLEBUTTON,OnToggleButton)
2940                    dbox.Add(but,(D[0]+1,D[1]+3),border=1)
2941                    UsedCols[D[1]+3] = True
2942                for i,D in enumerate(DistArray[c][:-1]): # label columns that are used
2943                    if UsedCols.get(i+3):
2944                        dbox.Add(wx.StaticText(cpnl,wx.ID_ANY,AtomLabels[D[0]]),
2945                                 (0,i+3),
2946                                 flag=wx.ALIGN_CENTER
2947                                 )
2948                dbox.Add(wx.StaticText(cpnl,wx.ID_ANY,'distance'),
2949                                 (0,2),
2950                                 flag=wx.ALIGN_CENTER
2951                                 )
2952                cbox.Add(dbox)
2953                G2G.HorizontalLine(cbox,cpnl)
2954            cpnl.SetSizer(cbox)
2955            cpnl.SetAutoLayout(1)
2956            cpnl.SetupScrolling()
2957            #cpnl.Bind(rw.EVT_RW_LAYOUT_NEEDED, self.OnLayoutNeeded) # needed if sizes change
2958            cpnl.Layout()
2959
2960            vbox.Add(cpnl, 1, wx.ALIGN_LEFT|wx.ALL|wx.EXPAND, 0)
2961
2962            btnsizer = wx.StdDialogButtonSizer()
2963            btn = wx.Button(dlg, wx.ID_OK, "Done")
2964            btn.SetDefault()
2965            btnsizer.AddButton(btn)
2966            btnsizer.Realize()
2967            vbox.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5)
2968            dlg.SetSizer(vbox)
2969            vbox.Fit(dlg)
2970            dlg.Layout()
2971
2972            dlg.CenterOnParent()
2973            dlg.ShowModal()
2974
2975#==============================================================================
2976####  _Exporter code starts here         ======================================
2977#==============================================================================
2978        # make sure required information is present
2979        self.CIFdate = dt.datetime.strftime(dt.datetime.now(),"%Y-%m-%dT%H:%M")
2980        if not self.CIFname: # Get a name for the CIF. If not defined, use the GPX name (save, if that is needed).
2981            if not self.G2frame.GSASprojectfile:
2982                self.G2frame.OnFileSaveas(None)
2983            if not self.G2frame.GSASprojectfile: return
2984            self.CIFname = os.path.splitext(
2985                os.path.split(self.G2frame.GSASprojectfile)[1]
2986                )[0]
2987            self.CIFname = self.CIFname.replace(' ','')
2988        # replace non-ASCII characters in CIFname with dots
2989        s = ''
2990        for c in self.CIFname:
2991            if ord(c) < 128:
2992                s += c
2993            else:
2994                s += '.'
2995        self.CIFname = s
2996       
2997        #=================================================================
2998        # write quick CIFs
2999        #=================================================================
3000        if phaseOnly: #====Phase only CIF ================================
3001            print('Writing CIF output to file '+self.filename)
3002            oneblock = True
3003            self.quickmode = True
3004            self.Write(' ')
3005            self.Write(70*'#')
3006            WriteCIFitem(self.fp, 'data_'+phaseOnly.replace(' ','_'))
3007            #phaseblk = self.Phases[phaseOnly] # pointer to current phase info
3008            # report the phase info
3009            WritePhaseInfo(phaseOnly)
3010            return
3011        elif histOnly: #====Histogram only CIF ================================
3012            print('Writing CIF output to file '+self.filename)
3013            hist = histOnly
3014            #histname = histOnly.replace(' ','')
3015            oneblock = True
3016            self.quickmode = True
3017            self.ifHKLF = False
3018            self.ifPWDR = True
3019            self.Write(' ')
3020            self.Write(70*'#')
3021            #phasenam = self.Phases.keys()[0]
3022            WriteCIFitem(self.fp, 'data_'+self.CIFname)
3023            if hist.startswith("PWDR"):
3024                WritePowderData(hist)
3025            elif hist.startswith("HKLF"):
3026                WriteSingleXtalData(hist)
3027            return
3028        #===============================================================================
3029        # setup for sequential fits here
3030        #===============================================================================
3031        seqmode = False
3032        seqHistList = self.G2frame.testSeqRefineMode()
3033        if seqHistList:
3034            if self.seqData is None:
3035                raise Exception('Use Export/Sequential project for sequential refinements')
3036            phaseWithHist = True   # include the phase in the same block as the histogram
3037            if len(self.Phases) > 1:
3038                phaseWithHist = False  # multiple phases per histogram
3039            seqmode = True
3040        #===============================================================================
3041        # the export process for a full CIF starts here (Sequential too)
3042        #===============================================================================
3043        # load saved CIF author name
3044        self.author = self.OverallParms['Controls'].get("Author",'?').strip()
3045        # initialize dict for Selection of Hist for unit cell reporting
3046        self.OverallParms['Controls']['CellHistSelection'] = self.OverallParms[
3047            'Controls'].get('CellHistSelection',{})
3048        self.CellHistSelection = self.OverallParms['Controls']['CellHistSelection']
3049        # create a dict with refined values and their uncertainties
3050        self.loadParmDict()
3051        # is there anything to export?
3052        if len(self.Phases) == len(self.powderDict) == len(self.xtalDict) == 0:
3053           self.G2frame.ErrorDialog(
3054               'Empty project',
3055               'Project does not contain any data or phases. Are they interconnected?')
3056           return
3057        if self.ExportSelect('ask'): return
3058        if not self.filename:
3059            print('No name supplied')
3060            return
3061        self.OpenFile()
3062       
3063        #if self.ExportSelect('default'): return
3064        # Someday: get restraint & constraint info
3065        #restraintDict = self.OverallParms.get('Restraints',{})
3066        #for i in  self.OverallParms['Constraints']:
3067        #    print i
3068        #    for j in self.OverallParms['Constraints'][i]:
3069        #        print j
3070
3071        self.quickmode = False # full CIF
3072        phasenam = None # include all phases
3073        # Will this require a multiblock CIF?
3074        if len(self.Phases) > 1:
3075            oneblock = False
3076        elif len(self.powderDict) + len(self.xtalDict) > 1:
3077            oneblock = False
3078        else: # one phase, one dataset, Full CIF
3079            oneblock = True
3080
3081        # check there is an instrument name for every histogram
3082        self.ifPWDR = False
3083        self.ifHKLF = False
3084        invalid = 0
3085        key3 = 'InstrName'
3086        for hist in self.Histograms:
3087            if hist.startswith("PWDR"):
3088                self.ifPWDR = True
3089                key2 = "Sample Parameters"
3090                d = self.Histograms[hist][key2]
3091            elif hist.startswith("HKLF"):
3092                self.ifHKLF = True
3093                key2 = "Instrument Parameters"
3094                d = self.Histograms[hist][key2][0]
3095            instrname = d.get(key3)
3096            if instrname is None:
3097                d[key3] = ''
3098                invalid += 1
3099            elif instrname.strip() == '':
3100                invalid += 1
3101            if hist.startswith("PWDR") and seqmode: break
3102        if invalid:
3103            #msg = ""
3104            #if invalid > 3: msg = (
3105            #    "\n\nNote: it may be faster to set the name for\n"
3106            #    "one histogram for each instrument and use the\n"
3107            #    "File/Copy option to duplicate the name"
3108            #    )
3109            if not EditInstNames(): return
3110
3111        # check for a distance-angle range search range for each phase
3112        for phasenam in sorted(self.Phases.keys()):
3113            #i = self.Phases[phasenam]['pId']
3114            phasedict = self.Phases[phasenam] # pointer to current phase info
3115            if 'DisAglCtls' not in phasedict['General']:
3116                dlg = G2G.DisAglDialog(
3117                    self.G2frame,
3118                    {},
3119                    phasedict['General'])
3120                if dlg.ShowModal() == wx.ID_OK:
3121                    phasedict['General']['DisAglCtls'] = dlg.GetData()
3122                else:
3123                    dlg.Destroy()
3124                    return
3125                dlg.Destroy()
3126                   
3127        # check if temperature values & pressure are defaulted
3128        default = 0
3129        for hist in self.Histograms:
3130            if hist.startswith("PWDR"):
3131                key2 = "Sample Parameters"
3132                T = self.Histograms[hist][key2].get('Temperature')
3133                if not T:
3134                    default += 1
3135                elif T == 300:
3136                    default += 1
3137                P = self.Histograms[hist][key2].get('Pressure')
3138                if not P:
3139                    default += 1
3140                elif P == 1:
3141                    default += 1
3142        if default > 0:
3143            dlg = wx.MessageDialog(
3144                self.G2frame,
3145                '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?',
3146                'Check T and P values',
3147                wx.OK|wx.CANCEL)
3148            ret = dlg.ShowModal()
3149            dlg.CenterOnParent()
3150            dlg.Destroy()
3151            if ret != wx.ID_OK: return
3152        if oneblock:
3153            # select a dataset to use (there should only be one set in one block,
3154            # but take whatever comes 1st)
3155            for hist in self.Histograms:
3156                histblk = self.Histograms[hist]
3157                if hist.startswith("PWDR"):
3158                    instnam = histblk["Sample Parameters"]['InstrName']
3159                    break # ignore all but 1st data histogram
3160                elif hist.startswith("HKLF"):
3161                    instnam = histblk["Instrument Parameters"][0]['InstrName']
3162                    break # ignore all but 1st data histogram
3163        # give the user a window to edit CIF contents
3164        if not self.author:
3165            self.author = self.OverallParms['Controls'].get("Author",'?').strip()
3166        if not self.author:
3167            if not EditAuthor(): return
3168        self.ValidateAscii([('Author name',self.author),]) # check for ASCII strings where needed, warn on problems
3169        self.shortauthorname = self.author.replace(',','').replace(' ','')[:20]
3170        self.cifdefs = wx.Dialog(
3171            self.G2frame,
3172            style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
3173        self.cifdefs.G2frame = self.G2frame
3174        self.cifdefs.CenterOnParent()
3175        EditCIFDefaults()
3176        if self.cifdefs.ShowModal() != wx.ID_OK:
3177            self.cifdefs.Destroy()
3178            return
3179        while self.ValidateAscii([('Author name',self.author),
3180                                  ]): # validate a few things as ASCII
3181            if self.cifdefs.ShowModal() != wx.ID_OK:
3182                self.cifdefs.Destroy()
3183                return
3184        self.cifdefs.Destroy()
3185        #======================================================================
3186        #---- Start writing the CIF - single block
3187        #======================================================================
3188        print('Writing CIF output to file '+self.filename+"...")
3189        if self.currentExportType == 'single' or self.currentExportType == 'powder':
3190            #======Data only CIF (powder/xtal) ====================================
3191            hist = self.histnam[0]
3192            self.CIFname = hist[5:40].replace(' ','')
3193            WriteCIFitem(self.fp, 'data_'+self.CIFname)
3194            if hist.startswith("PWDR"):
3195                WritePowderData(hist)
3196            elif hist.startswith("HKLF"):
3197                WriteSingleXtalData(hist)
3198            else:
3199                print ("should not happen")
3200        elif oneblock:
3201            #====Single block, data & phase CIF ===================================
3202            WriteCIFitem(self.fp, 'data_'+self.CIFname)
3203            if phasenam is None: # if not already selected, select the first phase (should be one)
3204                phasenam = self.Phases.keys()[0]
3205            #print 'phasenam',phasenam
3206            #phaseblk = self.Phases[phasenam] # pointer to current phase info
3207            instnam = instnam.replace(' ','')
3208            WriteCIFitem(self.fp, '_pd_block_id',
3209                         str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
3210                         str(self.shortauthorname) + "|" + instnam)
3211            WriteAudit()
3212            writeCIFtemplate(self.OverallParms['Controls'],'publ') # overall (publication) template
3213            WriteOverall()
3214            writeCIFtemplate(self.Phases[phasenam]['General'],'phase',phasenam) # write phase template
3215            # report the phase info
3216            WritePhaseInfo(phasenam,False)
3217            if hist.startswith("PWDR"):  # this is invoked for single-block CIFs
3218                # preferred orientation
3219                SH = FormatSH(phasenam)
3220                MD = FormatHAPpo(phasenam)
3221                if SH and MD:
3222                    WriteCIFitem(self.fp, '_pd_proc_ls_pref_orient_corr', SH + '\n' + MD)
3223                elif SH or MD:
3224                    WriteCIFitem(self.fp, '_pd_proc_ls_pref_orient_corr', SH + MD)
3225                else:
3226                    WriteCIFitem(self.fp, '_pd_proc_ls_pref_orient_corr', 'none')
3227                # report profile, since one-block: include both histogram and phase info (N.B. there is only 1 of each)
3228                WriteCIFitem(self.fp, '_pd_proc_ls_profile_function',
3229                    FormatInstProfile(histblk["Instrument Parameters"],histblk['hId'])
3230                    +'\n'+FormatPhaseProfile(phasenam))
3231
3232                histblk = self.Histograms[hist]["Sample Parameters"]
3233                writeCIFtemplate(histblk,'powder',histblk['InstrName']) # write powder template
3234                WritePowderData(hist)
3235            elif hist.startswith("HKLF"):
3236                histprm = self.Histograms[hist]["Instrument Parameters"][0]
3237                writeCIFtemplate(histprm,'single',histprm['InstrName']) # single crystal template
3238                WriteSingleXtalData(hist)
3239        elif seqHistList:
3240            #---- sequential fit project (multiblock)  ====================
3241            phasebyhistDict = {} # create a cross-reference to phases by histogram
3242            for phasenam in sorted(self.Phases.keys()):
3243                rId = phasedict['ranId']
3244                if rId in self.CellHistSelection: continue
3245                self.CellHistSelection[rId] = self._CellSelectHist(phasenam)
3246            nsteps = 1 + len(self.Phases) + len(self.powderDict) + len(self.xtalDict)
3247            try:
3248                dlg = wx.ProgressDialog('CIF progress','starting',nsteps,parent=self.G2frame)
3249                dlg.CenterOnParent()
3250
3251                # publication info
3252                step = 1
3253                dlg.Update(step,"Exporting overall section")
3254                WriteCIFitem(self.fp, '\ndata_'+self.CIFname+'_publ')
3255                WriteAudit()
3256                WriteCIFitem(self.fp, '_pd_block_id',
3257                             str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
3258                             str(self.shortauthorname) + "|Overall")
3259                writeCIFtemplate(self.OverallParms['Controls'],'publ') #insert the publication template
3260                # ``template_publ.cif`` or a modified version
3261                # overall info
3262                WriteCIFitem(self.fp, 'data_'+str(self.CIFname)+'_overall')
3263                WriteOverall('seq')
3264                hist = self.powderDict[sorted(self.powderDict.keys())[0]]
3265                instnam = self.Histograms[hist]["Sample Parameters"]['InstrName']
3266                writeCIFtemplate(self.seqSmplParms,'powder',instnam) # powder template
3267                instnam = instnam.replace(' ','')
3268                #============================================================
3269                if phaseWithHist:
3270                    WriteCIFitem(self.fp, '# POINTERS TO HISTOGRAM BLOCKS (Phase in histogram block)')
3271                else:
3272                    WriteCIFitem(self.fp, '# POINTERS TO HISTOGRAM BLOCKS (Phases pointer in histogram block)') 
3273                datablockidDict = {} # save block names here
3274                # loop over data blocks
3275                if len(self.powderDict) + len(self.xtalDict) > 1:
3276                    loopprefix = ''
3277                    WriteCIFitem(self.fp, 'loop_   _pd_block_diffractogram_id')
3278                else:
3279                    loopprefix = '_pd_block_diffractogram_id'
3280                for i in sorted(self.powderDict.keys()):
3281                    hist = self.powderDict[i]
3282                    j = self.Histograms[hist]['hId']
3283                    datablockidDict[hist] = (str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
3284                                             str(self.shortauthorname) + "|" +
3285                                             instnam + "_hist_"+str(j))
3286                    WriteCIFitem(self.fp, loopprefix,datablockidDict[hist])
3287                for i in sorted(self.xtalDict.keys()):
3288                    hist = self.xtalDict[i]
3289                    histblk = self.Histograms[hist]
3290                    instnam = histblk["Instrument Parameters"][0]['InstrName']
3291                    instnam = instnam.replace(' ','')
3292                    i = histblk['hId']
3293                    datablockidDict[hist] = (str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
3294                                             str(self.shortauthorname) + "|" +
3295                                             instnam + "_hist_"+str(i))
3296                    WriteCIFitem(self.fp, loopprefix,datablockidDict[hist])
3297                # setup and show sequential results table
3298                tblLabels,tblValues,tblSigs,tblTypes = mkSeqResTable('cif',seqHistList,self.seqData,
3299                                                    self.Phases,self.Histograms,self.Controls)
3300                WriteCIFitem(self.fp, '\n# Sequential results table') # (in case anyone can make sense of it)
3301                WriteCIFitem(self.fp, 'loop_   _gsas_seq_results_col_num _gsas_seq_results_col_label')
3302                for i,lbl in enumerate(tblLabels):
3303                    s = PutInCol(str(i),5)
3304                    if ' ' in lbl:
3305                        s += '"' + lbl + '"'
3306                    else:
3307                        s += lbl
3308                    WriteCIFitem(self.fp,"  "+s)
3309                s = 'loop_ '
3310                linelength = 120
3311                for i in range(len(tblLabels)):
3312                    if len(s) > linelength:
3313                        WriteCIFitem(self.fp,s)
3314                        s = '  '
3315                    s += " _gsas_seq_results_val" + str(i)
3316                WriteCIFitem(self.fp,s)
3317               
3318                for r in range(len(tblValues[0])):
3319                    s = ''
3320                    for c in range(len(tblLabels)):
3321                        if len(s) > linelength:
3322                            WriteCIFitem(self.fp,s)
3323                            s = '  '
3324                        sig = None
3325                        if tblSigs[c] is not None:
3326                            sig = tblSigs[c][r]
3327                           
3328                        if tblValues[c][r] is None:
3329                            if tblTypes[c] == 'int':
3330                                wid = 5
3331                            elif tblTypes[c] == 'str':
3332                                wid = 10
3333                            else:
3334                                wid = 12                               
3335                            s += PutInCol('.',wid)
3336                        elif sig is None and ',' in tblTypes[c]:
3337                            s += PutInCol(
3338                                ('{{:{}.{}f}}'.format(*tblTypes[c].split(','))).format(tblValues[c][r]),12)
3339                        elif tblTypes[c] == 'int':
3340                            s += PutInCol(str(tblValues[c][r]),5)
3341                        elif tblTypes[c] == 'str':
3342                            s += PutInCol(str(tblValues[c][r]),10)
3343                        elif sig is None and ',' in tblTypes[c]:
3344                            s += PutInCol(
3345                                ('{{:{}.{}f}}'.format(*tblTypes[c].split(','))).format(tblValues[c][r]),12)
3346                        elif sig is None and tblTypes[c] == 'float':
3347                            s += PutInCol('{:.6g}'.format(tblValues[c][r]),12)
3348                        elif sig:
3349                            s += PutInCol(G2mth.ValEsd(tblValues[c][r],sig),12)
3350                        else:
3351                            s += PutInCol(str(tblValues[c][r]),15)
3352                    WriteCIFitem(self.fp,s+'\n')
3353
3354                # sample template info & info for all phases
3355
3356                i = sorted(self.powderDict.keys())[0]
3357                hist = self.powderDict[i]
3358                histblk = self.Histograms[hist]
3359                if phaseWithHist:    # include sample info in overall block
3360                    phasenam = list(self.Phases.keys())[0]
3361                    writeCIFtemplate(self.Phases[phasenam]['General'],'phase',phasenam) # write phase template
3362                    WriteSeqOverallPhaseInfo(phasenam,histblk)
3363                else:
3364                    for j,phasenam in enumerate(sorted(self.Phases.keys())):
3365                        pId = self.Phases[phasenam]['pId']
3366                        WriteCIFitem(self.fp, '\n#'+78*'=')
3367                        WriteCIFitem(self.fp, 'data_'+self.CIFname+"_overall_phase"+str(j)+'\n')
3368                        writeCIFtemplate(self.Phases[phasenam]['General'],'phase',phasenam) # write phase template
3369                        WriteSeqOverallPhaseInfo(phasenam,histblk)
3370                       
3371                for i in sorted(self.powderDict.keys()):
3372                    hist = self.powderDict[i]
3373                    hId = self.Histograms[hist]['hId']
3374                    dlg.Update(step,"Exporting "+hist.strip())
3375                    histblk = self.Histograms[hist]
3376                    WriteCIFitem(self.fp, '# Information for histogram '+str(i)+': '+hist)
3377                    WriteCIFitem(self.fp, '\ndata_'+self.CIFname+"_pwd_"+str(i))
3378                    WriteCIFitem(self.fp, '_pd_block_id',datablockidDict[hist])
3379                    if not phaseWithHist:
3380                        WriteCIFitem(self.fp, '\n# POINTERS TO PHASE BLOCKS')
3381                        phaseBlockName = {}
3382
3383                        wtFrSum = 0.
3384                        for j,phasenam in enumerate(sorted(self.Phases.keys())):
3385                            if hist not in self.Phases[phasenam]['Histograms']: continue
3386                            if not self.Phases[phasenam]['Histograms'][hist]['Use']: continue
3387                            phFrac = self.Phases[phasenam]['Histograms'][hist]['Scale'][0]
3388                            phFracKey = str(self.Phases[phasenam]['pId'])+':'+str(hId)+':Scale'
3389                            phFrac = self.seqData[hist]['parmDict'].get(phFracKey,phFrac)
3390                            wtFrSum += phFrac * self.Phases[phasenam]['General']['Mass']
3391                        WriteCIFitem(self.fp, 'loop_ _pd_phase_id _pd_phase_block_id _pd_phase_mass_%')
3392                        for j,phasenam in enumerate(sorted(self.Phases.keys())):
3393                            pId = self.Phases[phasenam]['pId']
3394                            if hist not in self.Phases[phasenam]['Histograms']: continue
3395                            if not self.Phases[phasenam]['Histograms'][hist]['Use']: continue
3396                            if ' ' in phasenam:
3397                                s = PutInCol('"'+phasenam+'"',20)
3398                            else:
3399                                s = PutInCol(phasenam,20)
3400                            phaseBlockName[pId] = datablockidDict[hist]+'_p'+str(j+1)
3401                            phFrac = self.Phases[phasenam]['Histograms'][hist]['Scale'][0]
3402                            phFracKey = str(self.Phases[phasenam]['pId'])+':'+str(hId)+':Scale'
3403                            phFrac = self.seqData[hist]['parmDict'].get(phFracKey,phFrac)
3404                            wtFr = phFrac * self.Phases[phasenam]['General']['Mass'] / wtFrSum
3405                            if phFracKey in self.seqData[hist]['varyList']:
3406                                sig = self.seqData[hist]['sig'][self.seqData[hist]['varyList'].index(phFracKey)]
3407                                sig *= self.Phases[phasenam]['General']['Mass'] / wtFrSum
3408                            elif phFracKey in self.seqData[hist]['depParmDict']:
3409                                sig = self.seqData[hist]['depParmDict'][phFracKey][1]
3410                                sig *= self.Phases[phasenam]['General']['Mass'] / wtFrSum
3411                            else:
3412                                sig = -0.0001
3413                            WriteCIFitem(self.fp, "  "+ s + " " + phaseBlockName[pId] + "  " + G2mth.ValEsd(wtFr,sig))
3414                        PP = FormatInstProfile(histblk["Instrument Parameters"],histblk['hId'])
3415                        PP += '\n'
3416                        WriteCIFitem(self.fp, '_pd_proc_ls_profile_function',PP)
3417               
3418                    WritePowderData(hist,seq=True) # write background, data & reflections, some instrument & sample terms
3419                    WriteCIFitem(self.fp, '\n# PHASE INFO FOR HISTOGRAM '+hist)
3420                    for j,phasenam in enumerate(sorted(self.Phases.keys())):
3421                        pId = self.Phases[phasenam]['pId']
3422                        if hist not in self.Phases[phasenam]['Histograms']: continue
3423                        if not self.Phases[phasenam]['Histograms'][hist]['Use']: continue
3424                        WriteCIFitem(self.fp, '\n# phase info for '+str(phasenam) + ' follows')
3425                        if not phaseWithHist:
3426                            WriteCIFitem(self.fp, 'data_'+self.CIFname+"_hist"+str(i)+"_phase"+str(j))
3427                            WriteCIFitem(self.fp, '_pd_block_id',phaseBlockName[pId])
3428                            WriteCIFitem(self.fp, '')
3429
3430                        WriteSeqPhaseVals(phasenam,self.Phases[phasenam],pId,hist)
3431
3432                        # preferred orientation & profile terms
3433                        if self.ifPWDR:
3434                            #SH = FormatSH(phasenam)     # TODO: needs to use seqData
3435                            #MD = FormatHAPpo(phasenam)  # TODO: switch to seqData
3436                            #if SH and MD:
3437                            #    WriteCIFitem(self.fp, '_pd_proc_ls_pref_orient_corr', SH + '\n' + MD)
3438                            #elif SH or MD:
3439                            #    WriteCIFitem(self.fp, '_pd_proc_ls_pref_orient_corr', SH + MD)
3440                            #else:
3441                            #    WriteCIFitem(self.fp, '_pd_proc_ls_pref_orient_corr', 'none')
3442                            # report sample profile terms for all histograms with current phase
3443                            if phaseWithHist:
3444                                PP = FormatInstProfile(histblk["Instrument Parameters"],histblk['hId'])
3445                                PP += '\n'
3446                            else:
3447                                PP = ''
3448                            PP += FormatPhaseProfile(phasenam,hist)
3449                            WriteCIFitem(self.fp, '\n_pd_proc_ls_profile_function',PP)
3450            finally:
3451                dlg.Destroy()
3452        else:
3453            #---- multiblock: multiple phases and/or histograms ====================
3454            for phasenam in sorted(self.Phases.keys()):
3455                rId = phasedict['ranId']
3456                if rId in self.CellHistSelection: continue
3457                self.CellHistSelection[rId] = self._CellSelectHist(phasenam)
3458            nsteps = 1 + len(self.Phases) + len(self.powderDict) + len(self.xtalDict)
3459            dlg = wx.ProgressDialog('CIF progress','starting',nsteps,parent=self.G2frame)
3460            dlg.CenterOnParent()
3461
3462            # publication info
3463            step = 1
3464            dlg.Update(step,"Exporting overall section")
3465            WriteCIFitem(self.fp, '\ndata_'+self.CIFname+'_publ')
3466            WriteAudit()
3467            WriteCIFitem(self.fp, '_pd_block_id',
3468                         str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
3469                         str(self.shortauthorname) + "|Overall")
3470            writeCIFtemplate(self.OverallParms['Controls'],'publ') #insert the publication template
3471            # ``template_publ.cif`` or a modified version
3472            # overall info
3473            WriteCIFitem(self.fp, 'data_'+str(self.CIFname)+'_overall')
3474            WriteOverall()
3475            #============================================================
3476            WriteCIFitem(self.fp, '# POINTERS TO PHASE AND HISTOGRAM BLOCKS')
3477            datablockidDict = {} # save block names here -- N.B. check for conflicts between phase & hist names (unlikely!)
3478            # loop over phase blocks
3479            if len(self.Phases) > 1:
3480                loopprefix = ''
3481                WriteCIFitem(self.fp, 'loop_   _pd_phase_block_id')
3482            else:
3483                loopprefix = '_pd_phase_block_id'
3484
3485            for phasenam in sorted(self.Phases.keys()):
3486                i = self.Phases[phasenam]['pId']
3487                datablockidDict[phasenam] = (str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
3488                             'phase_'+ str(i) + '|' + str(self.shortauthorname))
3489                WriteCIFitem(self.fp, loopprefix,datablockidDict[phasenam])
3490            # loop over data blocks
3491            if len(self.powderDict) + len(self.xtalDict) > 1:
3492                loopprefix = ''
3493                WriteCIFitem(self.fp, 'loop_   _pd_block_diffractogram_id')
3494            else:
3495                loopprefix = '_pd_block_diffractogram_id'
3496            for i in sorted(self.powderDict.keys()):
3497                hist = self.powderDict[i]
3498                histblk = self.Histograms[hist]
3499                instnam = histblk["Sample Parameters"]['InstrName']
3500                instnam = instnam.replace(' ','')
3501                j = histblk['hId']
3502                datablockidDict[hist] = (str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
3503                                         str(self.shortauthorname) + "|" +
3504                                         instnam + "_hist_"+str(j))
3505                WriteCIFitem(self.fp, loopprefix,datablockidDict[hist])
3506            for i in sorted(self.xtalDict.keys()):
3507                hist = self.xtalDict[i]
3508                histblk = self.Histograms[hist]
3509                instnam = histblk["Instrument Parameters"][0]['InstrName']
3510                instnam = instnam.replace(' ','')
3511                i = histblk['hId']
3512                datablockidDict[hist] = (str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
3513                                         str(self.shortauthorname) + "|" +
3514                                         instnam + "_hist_"+str(i))
3515                WriteCIFitem(self.fp, loopprefix,datablockidDict[hist])
3516            #============================================================
3517            # loop over phases, exporting them
3518            for j,phasenam in enumerate(sorted(self.Phases.keys())):
3519                step += 1
3520                dlg.Update(step,"Exporting phase "+phasenam+' (#'+str(j+1)+')')
3521                i = self.Phases[phasenam]['pId']
3522                WriteCIFitem(self.fp, '\ndata_'+self.CIFname+"_phase_"+str(i))
3523                WriteCIFitem(self.fp, '# Information for phase '+str(i))
3524                WriteCIFitem(self.fp, '_pd_block_id',datablockidDict[phasenam])
3525                # report the phase
3526                writeCIFtemplate(self.Phases[phasenam]['General'],'phase',phasenam) # write phase template
3527                WritePhaseInfo(phasenam,False,False)
3528                # preferred orientation
3529                if self.ifPWDR:
3530                    SH = FormatSH(phasenam)
3531                    MD = FormatHAPpo(phasenam)
3532                    if SH and MD:
3533                        WriteCIFitem(self.fp, '_pd_proc_ls_pref_orient_corr', SH + '\n' + MD)
3534                    elif SH or MD:
3535                        WriteCIFitem(self.fp, '_pd_proc_ls_pref_orient_corr', SH + MD)
3536                    else:
3537                        WriteCIFitem(self.fp, '_pd_proc_ls_pref_orient_corr', 'none')
3538                # report sample profile terms for all histograms with current phase
3539                PP = FormatPhaseProfile(phasenam)
3540                if PP:
3541                    WriteCIFitem(self.fp, '_pd_proc_ls_profile_function',PP)
3542                self.ShowHstrainCells(phasenam,datablockidDict)
3543
3544            #============================================================
3545            # loop over histograms, exporting them
3546            # first, get atoms across all phases
3547            uniqueAtoms = []
3548            for phasenam in self.Phases:
3549                cx,ct,cs,cia = self.Phases[phasenam]['General']['AtomPtrs']
3550                for line in self.Phases[phasenam]['Atoms']:
3551                    atype = line[ct].strip()
3552                    if atype.find('-') != -1: atype = atype.split('-')[0]
3553                    if atype.find('+') != -1: atype = atype.split('+')[0]
3554                    atype = atype[0].upper()+atype[1:2].lower() # force case conversion
3555                    if atype == "D" or atype == "D": atype = "H"
3556                    if atype not in uniqueAtoms:
3557                        uniqueAtoms.append(atype)
3558
3559            for i in sorted(self.powderDict.keys()):
3560                hist = self.powderDict[i]
3561                histblk = self.Histograms[hist]
3562                if hist.startswith("PWDR"):
3563                    step += 1
3564                    dlg.Update(step,"Exporting "+hist.strip())
3565                    WriteCIFitem(self.fp, '\ndata_'+self.CIFname+"_pwd_"+str(i))
3566                    WriteCIFitem(self.fp, '# Information for histogram '+str(i)+': '+hist)
3567                    #instnam = histblk["Sample Parameters"]['InstrName']
3568                    # report instrumental profile terms
3569                    WriteCIFitem(self.fp, '_pd_proc_ls_profile_function',
3570                        FormatInstProfile(histblk["Instrument Parameters"],histblk['hId']))
3571                    WriteCIFitem(self.fp, '_pd_block_id',datablockidDict[hist])
3572                    histprm = self.Histograms[hist]["Sample Parameters"]
3573                    writeCIFtemplate(histprm,'powder',histprm['InstrName']) # powder template
3574                   
3575                    # get xray wavelength and compute & write f' & f''
3576                    lam = None
3577                    if 'X' in histblk['Instrument Parameters'][0]['Type'][0]:
3578                        for k in ('Lam','Lam1'):
3579                            if k in histblk['Instrument Parameters'][0]:
3580                                lam = histblk['Instrument Parameters'][0][k][0]
3581                                break
3582                    if lam:
3583                        keV = 12.397639/lam
3584                        WriteCIFitem(self.fp,'loop_')
3585                        for item in ('_atom_type_symbol','_atom_type_scat_dispersion_real',
3586                                         '_atom_type_scat_dispersion_imag','_atom_type_scat_dispersion_source'):
3587                            WriteCIFitem(self.fp,'     '+item)
3588                        for elem in HillSortElements(uniqueAtoms):
3589                            s = '  '
3590                            s += PutInCol(elem,4)
3591                            Orbs = G2el.GetXsectionCoeff(elem)
3592                            FP,FPP,Mu = G2el.FPcalc(Orbs, keV)
3593                            s += {:8.3f}{:8.3f}   https://subversion.xray.aps.anl.gov/pyGSAS/trunk/atmdata.py'.format(FP,FPP)
3594                            WriteCIFitem(self.fp,s.rstrip())
3595                        WriteCIFitem(self.fp,'')
3596                    WritePowderData(hist)
3597            for i in sorted(self.xtalDict.keys()):
3598                hist = self.xtalDict[i]
3599                histblk = self.Histograms[hist]
3600                if hist.startswith("HKLF"):
3601                    step += 1
3602                    dlg.Update(step,"Exporting "+hist.strip())
3603                    WriteCIFitem(self.fp, '\ndata_'+self.CIFname+"_sx_"+str(i))
3604                    #instnam = histblk["Instrument Parameters"][0]['InstrName']
3605                    WriteCIFitem(self.fp, '# Information for histogram '+str(i)+': '+hist)
3606                    WriteCIFitem(self.fp, '_pd_block_id',datablockidDict[hist])
3607                    histprm = self.Histograms[hist]["Instrument Parameters"][0]
3608                    writeCIFtemplate(histprm,'single',histprm['InstrName']) # single crystal template
3609                    WriteSingleXtalData(hist)
3610
3611            dlg.Destroy()
3612
3613        WriteCIFitem(self.fp, '#--' + 15*'eof--' + '#')
3614        print("...export completed")
3615        print('file '+self.fullpath)
3616        # end of CIF export
3617
3618class ExportProjectCIF(ExportCIF):
3619    '''Used to create a CIF of an entire project
3620
3621    :param wx.Frame G2frame: reference to main GSAS-II frame
3622    '''
3623    def __init__(self,G2frame):
3624        ExportCIF.__init__(self,
3625            G2frame=G2frame,
3626            formatName = 'Full CIF',
3627            extension='.cif',
3628            longFormatName = 'Export project as CIF'
3629            )
3630        self.exporttype = ['project']
3631
3632    def Exporter(self,event=None,seqData=None,Controls=None):
3633        self.seqData = seqData
3634        self.Controls = Controls
3635        self.InitExport(event)
3636        # load all of the tree into a set of dicts
3637        self.loadTree()
3638        self._Exporter(event=event)
3639        self.CloseFile()
3640
3641class ExportPhaseCIF(ExportCIF):
3642    '''Used to create a simple CIF with one phase. Uses exact same code as
3643    :class:`ExportCIF` except that `phaseOnly` is set for the Exporter
3644    Shows up in menu as Quick CIF.
3645
3646    :param wx.Frame G2frame: reference to main GSAS-II frame
3647    '''
3648    def __init__(self,G2frame):
3649        ExportCIF.__init__(self,
3650            G2frame=G2frame,
3651            formatName = 'Quick CIF',
3652            extension='.cif',
3653            longFormatName = 'Export one phase in CIF'
3654            )
3655        self.exporttype = ['phase']
3656        # CIF-specific items
3657        self.author = ''
3658
3659    def Exporter(self,event=None):
3660        # get a phase and file name
3661        # the export process starts here
3662        self.InitExport(event)
3663        # load all of the tree into a set of dicts
3664        self.loadTree()
3665        # create a dict with refined values and their uncertainties
3666        self.loadParmDict()
3667        self.multiple = True
3668        self.currentExportType = 'phase'
3669        if self.ExportSelect('ask'): return
3670        self.OpenFile()
3671        for name in self.phasenam:
3672            self._Exporter(event=event,phaseOnly=name)  #TODO: repeat for magnetic phase
3673        self.CloseFile()
3674
3675    def Writer(self,hist,phasenam,mode='w'):
3676        # set the project file name
3677        self.CIFname = os.path.splitext(
3678            os.path.split(self.G2frame.GSASprojectfile)[1]
3679            )[0]+'_'+phasenam+'_'+hist
3680        self.CIFname = self.CIFname.replace(' ','')
3681        self.OpenFile(mode=mode)
3682        self._Exporter(phaseOnly=phasenam)
3683        self.CloseFile()
3684
3685class ExportPwdrCIF(ExportCIF):
3686    '''Used to create a simple CIF containing diffraction data only. Uses exact same code as
3687    :class:`ExportCIF` except that `histOnly` is set for the Exporter
3688    Shows up in menu as Quick CIF.
3689
3690    :param wx.Frame G2frame: reference to main GSAS-II frame
3691    '''
3692    def __init__(self,G2frame):
3693        ExportCIF.__init__(self,
3694            G2frame=G2frame,
3695            formatName = 'Data-only CIF',
3696            extension='.cif',
3697            longFormatName = 'Export data as CIF'
3698            )
3699        if G2frame is None: raise AttributeError('CIF export requires data tree') # prevent use from Scriptable
3700        self.exporttype = ['powder']
3701        # CIF-specific items
3702        self.author = ''
3703
3704    def Exporter(self,event=None):
3705        self.InitExport(event)
3706        # load all of the tree into a set of dicts
3707        self.currentExportType = None
3708        self.loadTree()
3709        self.currentExportType = 'powder'
3710        # create a dict with refined values and their uncertainties
3711        self.loadParmDict()
3712        self.multiple = False
3713        if self.ExportSelect( # set export parameters
3714            AskFile='ask' # get a file name/directory to save in
3715            ): return
3716        self.OpenFile()
3717        self._Exporter(event=event,histOnly=self.histnam[0])
3718
3719    def Writer(self,hist,mode='w'):
3720        '''Used for histogram CIF export of a sequential fit.
3721        '''
3722        # set the project file name
3723        self.CIFname = os.path.splitext(
3724            os.path.split(self.G2frame.GSASprojectfile)[1]
3725            )[0]+'_'+hist
3726        self.CIFname = self.CIFname.replace(' ','')
3727        self.OpenFile(mode=mode)
3728        self._Exporter(histOnly=hist)
3729        if mode == 'w':
3730            print('CIF written to file '+self.fullpath)
3731        self.CloseFile()
3732
3733class ExportHKLCIF(ExportCIF):
3734    '''Used to create a simple CIF containing diffraction data only. Uses exact same code as
3735    :class:`ExportCIF` except that `histOnly` is set for the Exporter
3736    Shows up in menu as Quick CIF.
3737
3738    :param wx.Frame G2frame: reference to main GSAS-II frame
3739    '''
3740    def __init__(self,G2frame):
3741        ExportCIF.__init__(self,
3742            G2frame=G2frame,
3743            formatName = 'Data-only CIF',
3744            extension='.cif',
3745            longFormatName = 'Export data as CIF'
3746            )
3747        self.exporttype = ['single']
3748        # CIF-specific items
3749        self.author = ''
3750
3751    def Exporter(self,event=None):
3752        self.InitExport(event)
3753        # load all of the tree into a set of dicts
3754        self.currentExportType = None
3755        self.loadTree()
3756        self.currentExportType = 'single'
3757        # create a dict with refined values and their uncertainties
3758        self.loadParmDict()
3759        self.multiple = False
3760        if self.ExportSelect( # set export parameters
3761            AskFile='ask' # get a file name/directory to save in
3762            ): return
3763        self.OpenFile()
3764        self._Exporter(event=event,histOnly=self.histnam[0])
3765
3766#===============================================================================
3767# misc CIF utilities
3768#===============================================================================
3769def PickleCIFdict(fil):
3770    '''Loads a CIF dictionary, cherry picks out the items needed
3771    by local code and sticks them into a python dict and writes
3772    that dict out as a pickle file for later reuse.
3773    If the write fails a warning message is printed,
3774    but no exception occurs.
3775
3776    :param str fil: file name of CIF dictionary, will usually end
3777      in .dic
3778    :returns: the dict with the definitions
3779    '''
3780    import CifFile as cif # PyCifRW from James Hester
3781    cifdic = {}
3782    try:
3783        fp = open(fil,'r')             # patch: open file to avoid windows bug
3784        dictobj = cif.CifDic(fp)
3785        fp.close()
3786    except IOError:
3787        dictobj = cif.CifDic(fil)
3788    if DEBUG: print('loaded '+fil)
3789    for item in dictobj.keys():
3790        cifdic[item] = {}
3791        for j in (
3792            '_definition','_type',
3793            '_enumeration',
3794            '_enumeration_detail',
3795            '_enumeration_range'):
3796            if dictobj[item].get(j):
3797                cifdic[item][j] = dictobj[item][j]
3798    try:
3799        fil = os.path.splitext(fil)[0]+'.cpickle'
3800        fp = open(fil,'wb')
3801        pickle.dump(cifdic,fp)
3802        fp.close()
3803        if DEBUG: print('wrote '+fil)
3804    except:
3805        print ('Unable to write '+fil)
3806    return cifdic
3807
3808def LoadCIFdic():
3809    '''Create a composite core+powder CIF lookup dict containing
3810    information about all items in the CIF dictionaries, loading
3811    pickled files if possible. The routine looks for files
3812    named cif_core.cpickle and cif_pd.cpickle in every
3813    directory in the path and if they are not found, files
3814    cif_core.dic and/or cif_pd.dic are read.
3815
3816    :returns: the dict with the definitions
3817    '''
3818    cifdic = {}
3819    for ftyp in "cif_core","cif_pd":
3820        for loc in sys.path:
3821            fil = os.path.join(loc,ftyp+".cpickle")
3822            if not os.path.exists(fil): continue
3823            fp = open(fil,'rb')
3824            try:
3825                cifdic.update(pickle.load(fp))
3826                if DEBUG: print('reloaded '+fil)
3827                break
3828            finally:
3829                fp.close()
3830        else:
3831            for loc in sys.path:
3832                fil = os.path.join(loc,ftyp+".dic")
3833                if not os.path.exists(fil): continue
3834                #try:
3835                if True:
3836                    cifdic.update(PickleCIFdict(fil))
3837                    break
3838                #except:
3839                #    pass
3840            else:
3841                print('Could not load '+ftyp+' dictionary')
3842    return cifdic
3843
3844class CIFdefHelp(wx.Button):
3845    '''Create a help button that displays help information on
3846    the current data item
3847
3848    :param parent: the panel which will be the parent of the button
3849    :param str msg: the help text to be displayed
3850    :param wx.Dialog helpwin: Frame for CIF editing dialog
3851    :param wx.TextCtrl helptxt: TextCtrl where help text is placed
3852    '''
3853    def __init__(self,parent,msg,helpwin,helptxt):
3854        wx.Button.__init__(self,parent,wx.ID_HELP)
3855        self.Bind(wx.EVT_BUTTON,self._onPress)
3856        self.msg=msg
3857        self.parent = parent
3858        self.helpwin = helpwin
3859        self.helptxt = helptxt
3860    def _onPress(self,event):
3861        'Respond to a button press by displaying the requested text'
3862        try:
3863            ww,wh = self.helpwin.GetSize()
3864            ow,oh = self.helptxt.GetSize()
3865            self.helptxt.SetLabel(self.msg)
3866            self.helptxt.Wrap(ww-10)
3867            w,h = self.helptxt.GetSize()
3868            if h > oh: # resize the help area if needed, but avoid changing width
3869                self.helptxt.SetMinSize((ww,h))
3870                self.helpwin.GetSizer().Fit(self.helpwin)
3871        except: # error posting help, ignore
3872            return
3873
3874def CIF2dict(cf):
3875    '''copy the contents of a CIF out from a PyCifRW block object
3876    into a dict
3877
3878    :returns: cifblk, loopstructure where cifblk is a dict with
3879      CIF items and loopstructure is a list of lists that defines
3880      which items are in which loops.
3881    '''
3882    blk = cf.keys()[0] # assume templates are a single CIF block, use the 1st
3883    try:
3884        loopstructure = cf[blk].loopnames()[:] # copy over the list of loop contents
3885    except AttributeError:
3886        loopstructure = [j[:] for j in cf[blk].loops.values()] # method replaced?
3887    dblk = {}
3888    for item in cf[blk].keys(): # make a copy of all the items in the block
3889        dblk[item] = cf[blk][item]
3890    return dblk,loopstructure
3891
3892def dict2CIF(dblk,loopstructure,blockname='Template'):
3893    '''Create a PyCifRW CIF object containing a single CIF
3894    block object from a dict and loop structure list.
3895
3896    :param dblk: a dict containing values for each CIF item
3897    :param list loopstructure: a list of lists containing the contents of
3898      each loop, as an example::
3899
3900         [ ["_a","_b"], ["_c"], ["_d_1","_d_2","_d_3"]]
3901
3902      this describes a CIF with this type of structure::
3903
3904        loop_ _a _b <a1> <b1> <a2> ...
3905        loop_ _c <c1> <c2>...
3906        loop _d_1 _d_2 _d_3 ...
3907
3908      Note that the values for each looped CIF item, such as _a,
3909      are contained in a list, for example as cifblk["_a"]
3910
3911    :param str blockname: an optional name for the CIF block.
3912      Defaults to 'Template'
3913
3914    :returns: the newly created PyCifRW CIF object
3915    '''
3916
3917    import CifFile as cif # PyCifRW from James Hester
3918    # compile a 'list' of items in loops
3919    loopnames = set()
3920    for i in loopstructure:
3921        loopnames |= set(i)
3922    # create a new block
3923    newblk = cif.CifBlock()
3924    # add the looped items
3925    for keys in loopstructure:
3926        vals = []
3927        for key in keys:
3928            vals.append(dblk[key])
3929        newblk.AddCifItem(([keys],[vals]))
3930    # add the non-looped items
3931    for item in dblk:
3932        if item in loopnames: continue
3933        newblk[item] = dblk[item]
3934    # create a CIF and add the block
3935    newcf = cif.CifFile()
3936    newcf[blockname] = newblk
3937    return newcf
3938
3939
3940class EditCIFtemplate(wx.Dialog):
3941    '''Create a dialog for editing a CIF template. The edited information is
3942    placed in cifblk. If the CIF is saved as a file, the name of that file
3943    is saved as ``self.newfile``.
3944
3945    :param wx.Frame parent: parent frame or None
3946    :param cifblk: dict or PyCifRW block containing values for each CIF item
3947    :param list loopstructure: a list of lists containing the contents of
3948      each loop, as an example::
3949
3950         [ ["_a","_b"], ["_c"], ["_d_1","_d_2","_d_3"]]
3951
3952      this describes a CIF with this type of structure::
3953
3954        loop_ _a _b <a1> <b1> <a2> ...
3955        loop_ _c <c1> <c2>...
3956        loop _d_1 _d_2 _d_3 ...
3957
3958      Note that the values for each looped CIF item, such as _a,
3959      are contained in a list, for example as cifblk["_a"]
3960
3961    :param str defaultname: specifies the default file name to be used for
3962      saving the CIF.
3963    '''
3964    def __init__(self,parent,cifblk,loopstructure,defaultname):
3965        OKbuttons = []
3966        self.cifblk = cifblk
3967        self.loopstructure = loopstructure
3968        self.newfile = None
3969        self.defaultname = defaultname
3970        self.G2frame = parent.G2frame
3971        global CIFdic  # once this is loaded, keep it around
3972        if CIFdic is None:
3973            CIFdic = LoadCIFdic()
3974        wx.Dialog.__init__(self,parent,style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
3975
3976        # define widgets that will be needed during panel creation
3977        self.helptxt = wx.StaticText(self,wx.ID_ANY,"")
3978        savebtn = wx.Button(self, wx.ID_CLOSE, "Save as template")
3979        OKbuttons.append(savebtn)
3980        savebtn.Bind(wx.EVT_BUTTON,self._onSave)
3981        OKbtn = wx.Button(self, wx.ID_OK, "Use")
3982        OKbtn.Bind(wx.EVT_BUTTON, lambda x: self.EndModal(wx.ID_OK))
3983        OKbtn.SetDefault()
3984        OKbuttons.append(OKbtn)
3985
3986        self.SetTitle('Edit items in CIF template')
3987        vbox = wx.BoxSizer(wx.VERTICAL)
3988        cpnl = EditCIFpanel(self,cifblk,loopstructure,CIFdic,OKbuttons,size=(300,300))
3989        vbox.Add(cpnl, 1, wx.ALIGN_LEFT|wx.ALL|wx.EXPAND, 0)
3990        G2G.HorizontalLine(vbox,self)
3991        vbox.Add(self.helptxt, 0, wx.EXPAND|wx.ALL, 5)
3992        G2G.HorizontalLine(vbox,self)
3993        btnsizer = wx.BoxSizer(wx.HORIZONTAL)
3994        btn = wx.Button(self, wx.ID_CANCEL)
3995        btnsizer.Add(btn,0,wx.ALIGN_CENTER|wx.ALL)
3996        btnsizer.Add(savebtn,0,wx.ALIGN_CENTER|wx.ALL)
3997        btnsizer.Add(OKbtn,0,wx.ALIGN_CENTER|wx.ALL)
3998        vbox.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5)
3999        self.SetSizer(vbox)
4000        vbox.Fit(self)
4001    def Post(self):
4002        '''Display the dialog
4003
4004        :returns: True unless Cancel has been pressed.
4005        '''
4006        return (self.ShowModal() == wx.ID_OK)
4007    def _onSave(self,event):
4008        'Save CIF entries in a template file'
4009        pth = G2G.GetExportPath(self.G2frame)
4010        dlg = wx.FileDialog(
4011            self, message="Save as CIF template",
4012            defaultDir=pth,
4013            defaultFile=self.defaultname,
4014            wildcard="CIF (*.cif)|*.cif",
4015            style=wx.FD_SAVE)
4016        val = (dlg.ShowModal() == wx.ID_OK)
4017        fil = dlg.GetPath()
4018        dlg.Destroy()
4019        if val: # ignore a Cancel button
4020            fil = os.path.splitext(fil)[0]+'.cif' # force extension
4021            fp = open(fil,'w')
4022            newcf = dict2CIF(self.cifblk,self.loopstructure)
4023            fp.write(newcf.WriteOut())
4024            fp.close()
4025            self.newfile = fil
4026            self.EndModal(wx.ID_OK)
4027
4028class EditCIFpanel(wxscroll.ScrolledPanel):
4029    '''Creates a scrolled panel for editing CIF template items
4030
4031    :param wx.Frame parent: parent frame where panel will be placed
4032    :param cifblk: dict or PyCifRW block containing values for each CIF item
4033    :param list loopstructure: a list of lists containing the contents of
4034      each loop, as an example::
4035
4036         [ ["_a","_b"], ["_c"], ["_d_1","_d_2","_d_3"]]
4037
4038      this describes a CIF with this type of structure::
4039
4040        loop_ _a _b <a1> <b1> <a2> ...
4041        loop_ _c <c1> <c2>...
4042        loop _d_1 _d_2 _d_3 ...
4043
4044      Note that the values for each looped CIF item, such as _a,
4045      are contained in a list, for example as cifblk["_a"]
4046
4047    :param dict cifdic: optional CIF dictionary definitions
4048    :param list OKbuttons: A list of wx.Button objects that should
4049      be disabled when information in the CIF is invalid
4050    :param (other): optional keyword parameters for wx.ScrolledPanel
4051    '''
4052    def __init__(self, parent, cifblk, loopstructure, cifdic={}, OKbuttons=[], **kw):
4053        self.parent = parent
4054        wxscroll.ScrolledPanel.__init__(self, parent, wx.ID_ANY, **kw)
4055        self.vbox = None
4056        self.AddDict = None
4057        self.cifdic = cifdic
4058        self.cifblk = cifblk
4059        self.loops = loopstructure
4060        self.parent = parent
4061        self.LayoutCalled = False
4062        self.parentOKbuttons = OKbuttons
4063        self.ValidatedControlsList = []
4064        self.G2frame = parent.G2frame
4065        self.height = G2G.getTextSize('?')[1]
4066        #print('height is ',self.height)
4067        self._fill()
4068    def _fill(self):
4069        'Fill the scrolled panel with widgets for each CIF item'
4070        wx.BeginBusyCursor()
4071        self.AddDict = {}
4072        self.ValidatedControlsList = []
4073        # delete any only contents
4074        if self.vbox:
4075            if 'phoenix' in wx.version():
4076                self.vbox.Clear(True)
4077            else:
4078                self.vbox.DeleteWindows()
4079            self.vbox = None
4080            self.Update()
4081        vbox = wx.BoxSizer(wx.VERTICAL)
4082        self.vbox = vbox
4083        # compile a 'list' of items in loops
4084        loopnames = set()
4085        for i in self.loops:
4086            loopnames |= set(i)
4087        # post the looped CIF items
4088        for lnum,lp in enumerate(self.loops):
4089            hbox = wx.BoxSizer(wx.HORIZONTAL)
4090            hbox.Add(wx.StaticText(self,wx.ID_ANY,'Loop '+str(lnum+1)))
4091            vbox.Add(hbox)
4092            but = wx.Button(self,wx.ID_ANY,"Add row")
4093            self.AddDict[but]=lnum
4094
4095            hbox.Add(but)
4096            but.Bind(wx.EVT_BUTTON,self.OnAddRow)
4097            fbox = wx.GridBagSizer(0, 0)
4098            vbox.Add(fbox)
4099            rows = 0
4100            for i,item in enumerate(lp):
4101                txt = wx.StaticText(self,wx.ID_ANY,item+"  ")
4102                fbox.Add(txt,(0,i+1))
4103                for j,val in enumerate(self.cifblk[item]):
4104                    ent = self.CIFEntryWidget(self.cifblk[item],j,item)
4105                    #fbox.Add(ent,(j+2,i+1),flag=wx.EXPAND|wx.ALL)
4106                    fbox.Add(ent,(j+1,i+1),flag=wx.EXPAND|wx.ALL)
4107                if self.cifdic.get(item):
4108                    df = self.cifdic[item].get('_definition')
4109                    if df:
4110                        try:
4111                            txt.SetToolTip(G2IO.trim(df))
4112                        except:
4113                            txt.SetToolTipString(G2IO.trim(df))
4114                        but = CIFdefHelp(self,
4115                                         "Definition for "+item+":\n\n"+G2IO.trim(df),
4116                                         self.parent,
4117                                         self.parent.helptxt)
4118                        fbox.Add(but,(j+2,i+1),flag=wx.ALIGN_CENTER)
4119                rows = max(rows,len(self.cifblk[item]))
4120            for i in range(rows):
4121                txt = wx.StaticText(self,wx.ID_ANY,str(i+1))
4122                fbox.Add(txt,(i+1,0))
4123            line = wx.StaticLine(self,wx.ID_ANY, size=(-1,3), style=wx.LI_HORIZONTAL)
4124            vbox.Add(line, 0, wx.EXPAND|wx.ALL, 10)
4125
4126        # post the non-looped CIF items
4127        for item in sorted(self.cifblk.keys()):
4128            if item not in loopnames:
4129                hbox = wx.BoxSizer(wx.HORIZONTAL)
4130                vbox.Add(hbox)
4131                txt = wx.StaticText(self,wx.ID_ANY,item)
4132                hbox.Add(txt)
4133                ent = self.CIFEntryWidget(self.cifblk,item,item)
4134                hbox.Add(ent)
4135                if self.cifdic.get(item):
4136                    df = self.cifdic[item].get('_definition')
4137                    if df:
4138                        try:
4139                            txt.SetToolTip(G2IO.trim(df))
4140                        except:
4141                            txt.SetToolTipString(G2IO.trim(df))
4142                        but = CIFdefHelp(self,
4143                                         "Definition for "+item+":\n\n"+G2IO.trim(df),
4144                                         self.parent,
4145                                         self.parent.helptxt)
4146                        hbox.Add(but,0,wx.ALL,2)
4147        self.SetSizer(vbox)
4148        #vbox.Fit(self.parent)
4149        self.SetAutoLayout(1)
4150        self.SetupScrolling()
4151        self.Bind(rw.EVT_RW_LAYOUT_NEEDED, self.OnLayoutNeeded)
4152        self.Layout()
4153        wx.EndBusyCursor()
4154    def OnLayoutNeeded(self,event):
4155        '''Called when an update of the panel layout is needed. Calls
4156        self.DoLayout after the current operations are complete using
4157        CallAfter. This is called only once, according to flag
4158        self.LayoutCalled, which is cleared in self.DoLayout.
4159        '''
4160        if self.LayoutCalled: return # call already queued
4161        wx.CallAfter(self.DoLayout) # queue a call
4162        self.LayoutCalled = True
4163    def DoLayout(self):
4164        '''Update the Layout and scroll bars for the Panel. Clears
4165        self.LayoutCalled so that next change to panel can
4166        request a new update
4167        '''
4168        wx.BeginBusyCursor()
4169        self.Layout()
4170        self.SetupScrolling()
4171        wx.EndBusyCursor()
4172        self.LayoutCalled = False
4173    def OnAddRow(self,event):
4174        'add a row to a loop'
4175        lnum = self.AddDict.get(event.GetEventObject())
4176        if lnum is None: return
4177        for item in self.loops[lnum]:
4178            self.cifblk[item].append('?')
4179        self._fill()
4180
4181    def ControlOKButton(self,setvalue):
4182        '''Enable or Disable the OK button(s) for the dialog. Note that this is
4183        passed into the ValidatedTxtCtrl for use by validators.
4184
4185        :param bool setvalue: if True, all entries in the dialog are
4186          checked for validity. The first invalid control triggers
4187          disabling of buttons.
4188          If False then the OK button(s) are disabled with no checking
4189          of the invalid flag for each control.
4190        '''
4191        if setvalue: # turn button on, do only if all controls show as valid
4192            for ctrl in self.ValidatedControlsList:
4193                if ctrl.invalid:
4194                    for btn in self.parentOKbuttons:
4195                        btn.Disable()
4196                    return
4197            else:
4198                for btn in self.parentOKbuttons:
4199                    btn.Enable()
4200        else:
4201            for btn in self.parentOKbuttons:
4202                btn.Disable()
4203
4204    def CIFEntryWidget(self,dct,item,dataname):
4205        '''Create an entry widget for a CIF item. Use a validated entry for numb values
4206        where int is required when limits are integers and floats otherwise.
4207        At present this does not allow entry of the special CIF values of "." and "?" for
4208        numerical values and highlights them as invalid.
4209        Use a selection widget when there are specific enumerated values for a string.
4210        '''
4211        if self.cifdic.get(dataname):
4212            if self.cifdic[dataname].get('_enumeration'):
4213                values = ['?']+self.cifdic[dataname]['_enumeration']
4214                choices = ['undefined']
4215                for i in self.cifdic[dataname].get('_enumeration_detail',values):
4216                    choices.append(G2IO.trim(i))
4217                ent = G2G.EnumSelector(self, dct, item, choices, values, size=(200, -1))
4218                return ent
4219            if self.cifdic[dataname].get('_type') == 'numb':
4220                mn = None
4221                mx = None
4222                hint = int
4223                if self.cifdic[dataname].get('_enumeration_range'):
4224                    rng = self.cifdic[dataname]['_enumeration_range'].split(':')
4225                    if '.' in rng[0] or '.' in rng[1]: hint = float
4226                    if rng[0]: mn = hint(rng[0])
4227                    if rng[1]: mx = hint(rng[1])
4228                    ent = G2G.ValidatedTxtCtrl(
4229                        self,dct,item,typeHint=hint,xmin=mn,xmax=mx,
4230                        CIFinput=True,ASCIIonly=True,
4231                        OKcontrol=self.ControlOKButton)
4232                    self.ValidatedControlsList.append(ent)
4233                    return ent
4234        rw1 = rw.ResizeWidget(self)
4235        ent = G2G.ValidatedTxtCtrl(
4236            rw1,dct,item,size=(100, self.height+5),
4237            style=wx.TE_MULTILINE|wx.TE_PROCESS_ENTER,
4238            CIFinput=True,ASCIIonly=True,
4239            OKcontrol=self.ControlOKButton)
4240        self.ValidatedControlsList.append(ent)
4241        return rw1
4242
4243class CIFtemplateSelect(wx.BoxSizer):
4244    '''Create a set of buttons to show, select and edit a CIF template
4245
4246    :param frame: wx.Frame object of parent
4247    :param panel: wx.Panel object where widgets should be placed
4248    :param str tmplate: one of 'publ', 'phase', or 'instrument' to determine
4249      the type of template
4250    :param dict G2dict: GSAS-II dict where CIF should be placed. The key
4251      "CIF_template" will be used to store either a list or a string.
4252      If a list, it will contain a dict and a list defining loops. If
4253      an str, it will contain a file name.
4254    :param function repaint: reference to a routine to be called to repaint
4255      the frame after a change has been made
4256    :param str title: A line of text to show at the top of the window
4257    :param str defaultname: specifies the default file name to be used for
4258      saving the CIF.
4259    '''
4260    def __init__(self,frame,panel,tmplate,G2dict, repaint, title, defaultname=''):
4261        wx.BoxSizer.__init__(self,wx.VERTICAL)
4262        self.cifdefs = frame
4263        self.dict = G2dict
4264        self.repaint = repaint
4265        self.G2frame = frame.G2frame
4266        templateDefName = 'template_'+tmplate+'.cif'
4267        self.CIF = G2dict.get("CIF_template")
4268        if defaultname:
4269            self.defaultname = G2obj.StripUnicode(defaultname)
4270            self.defaultname = re.sub(r'[^a-zA-Z0-9_-]','',self.defaultname)
4271            self.defaultname = tmplate + "_" + self.defaultname + ".cif"
4272        else:
4273            self.defaultname = ''
4274
4275        txt = wx.StaticText(panel,wx.ID_ANY,title)
4276        self.Add(txt,0,wx.ALIGN_CENTER)
4277        # change font on title
4278        txtfnt = txt.GetFont()
4279        txtfnt.SetWeight(wx.BOLD)
4280        txtfnt.SetPointSize(2+txtfnt.GetPointSize())
4281        txt.SetFont(txtfnt)
4282        self.Add((-1,3))
4283
4284        if not self.CIF: # empty or None
4285            for pth in [os.getcwd()]+sys.path:
4286                fil = os.path.join(pth,self.defaultname)
4287                if os.path.exists(fil) and self.defaultname:
4288                    self.CIF = fil
4289                    CIFtxt = "Template: "+self.defaultname
4290                    break
4291            else:
4292                for pth in sys.path:
4293                    fil = os.path.join(pth,templateDefName)
4294                    if os.path.exists(fil):
4295                        self.CIF = fil
4296                        CIFtxt = "Template: "+templateDefName
4297                        break
4298                else:
4299                    print("Default CIF template "+self.defaultname+' not found in path!')
4300                    self.CIF = None
4301                    CIFtxt = "none! (No template found)"
4302        elif type(self.CIF) is not list and type(self.CIF) is not tuple:
4303            if not os.path.exists(self.CIF):
4304                print("Error: template file has disappeared: "+self.CIF)
4305                self.CIF = None
4306                CIFtxt = "none! (file not found)"
4307            else:
4308                if len(self.CIF) < 50:
4309                    CIFtxt = "File: "+self.CIF
4310                else:
4311                    CIFtxt = "File: ..."+self.CIF[-50:]
4312        else:
4313            CIFtxt = "Template is customized"
4314        # show template source
4315        self.Add(wx.StaticText(panel,wx.ID_ANY,CIFtxt))
4316        # show str, button to select file; button to edit (if CIF defined)
4317        but = wx.Button(panel,wx.ID_ANY,"Select Template File")
4318        but.Bind(wx.EVT_BUTTON,self._onGetTemplateFile)
4319        hbox =  wx.BoxSizer(wx.HORIZONTAL)
4320        hbox.Add(but,0,0,2)
4321        but = wx.Button(panel,wx.ID_ANY,"Edit Template")
4322        but.Bind(wx.EVT_BUTTON,self._onEditTemplateContents)
4323        if self.CIF is None: but.Disable() # nothing to edit!
4324        hbox.Add(but,0,0,2)
4325        self.Add(hbox)
4326    def _onGetTemplateFile(self,event):
4327        'select a template file'
4328        pth = G2G.GetImportPath(self.G2frame)
4329        if not pth: pth = '.'
4330        dlg = wx.FileDialog(
4331            self.cifdefs, message="Read CIF template file",
4332            defaultDir=pth,
4333            defaultFile=self.defaultname,
4334            wildcard="CIF (*.cif)|*.cif",
4335            style=wx.FD_OPEN)
4336        ret = dlg.ShowModal()
4337        fil = dlg.GetPath()
4338        dlg.Destroy()
4339        if ret == wx.ID_OK:
4340            cf = G2obj.ReadCIF(fil)
4341            if len(cf.keys()) == 0:
4342                raise Exception("No CIF data_ blocks found")
4343            if len(cf.keys()) != 1:
4344                raise Exception('Error, CIF Template has more than one block: '+fil)
4345            self.dict["CIF_template"] = fil
4346            self.repaint() #EditCIFDefaults()
4347
4348    def _onEditTemplateContents(self,event):
4349        'Called to edit the contents of a CIF template'
4350        if type(self.CIF) is list or  type(self.CIF) is tuple:
4351            dblk,loopstructure = copy.deepcopy(self.CIF) # don't modify original
4352        else:
4353            cf = G2obj.ReadCIF(self.CIF)
4354            dblk,loopstructure = CIF2dict(cf)
4355        dlg = EditCIFtemplate(self.cifdefs,dblk,loopstructure,self.defaultname)
4356        val = dlg.Post()
4357        if val:
4358            if dlg.newfile: # results saved in file
4359                self.dict["CIF_template"] = dlg.newfile
4360            else:
4361                self.dict["CIF_template"] = [dlg.cifblk,dlg.loopstructure]
4362            self.repaint() #EditCIFDefaults() # note that this does a dlg.Destroy()
4363        else:
4364            dlg.Destroy()
4365
4366#===============================================================================
4367# end of misc CIF utilities
4368#===============================================================================
Note: See TracBrowser for help on using the repository browser.