source: trunk/exports/G2export_CIF.py @ 5047

Last change on this file since 5047 was 5047, checked in by toby, 17 months ago

CIF export fixes; work around for IsModified?() not showing changes

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Author Revision URL Id
File size: 203.2 KB
Line 
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3########### SVN repository information ###################
4# $Date: 2021-10-14 21:59:02 +0000 (Thu, 14 Oct 2021) $
5# $Author: toby $
6# $Revision: 5047 $
7# $URL: trunk/exports/G2export_CIF.py $
8# $Id: G2export_CIF.py 5047 2021-10-14 21:59:02Z 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: 5047 $")
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            if len(self.cifdefs.GetChildren()) > 0:
2701                saveSize = self.cifdefs.GetSize()
2702                self.cifdefs.DestroyChildren()
2703            else:
2704                saveSize = None
2705            self.cifdefs.SetTitle('Edit CIF settings')
2706            vbox = wx.BoxSizer(wx.VERTICAL)
2707            vbox.Add(wx.StaticText(self.cifdefs, wx.ID_ANY,'Creating file '+self.filename))
2708            but = wx.Button(self.cifdefs, wx.ID_ANY,'Edit CIF Author')
2709            but.Bind(wx.EVT_BUTTON,EditAuthor)
2710            vbox.Add(but,0,wx.ALIGN_CENTER,3)
2711            but = wx.Button(self.cifdefs, wx.ID_ANY,'Edit Instrument Name(s)')
2712            but.Bind(wx.EVT_BUTTON,EditInstNames)
2713            vbox.Add(but,0,wx.ALIGN_CENTER,3)
2714            cpnl = wxscroll.ScrolledPanel(self.cifdefs,size=(300,300))
2715            cbox = wx.BoxSizer(wx.VERTICAL)
2716            G2G.HorizontalLine(cbox,cpnl)
2717            cbox.Add(
2718                CIFtemplateSelect(self.cifdefs,
2719                                  cpnl,'publ',self.OverallParms['Controls'],
2720                                  EditCIFDefaults,
2721                                  "Publication (overall) template",
2722                                  ),
2723                0,wx.EXPAND|wx.ALIGN_LEFT|wx.ALL)
2724            for phasenam in sorted(self.Phases.keys()):
2725                G2G.HorizontalLine(cbox,cpnl)
2726                title = 'Phase '+phasenam
2727                phasedict = self.Phases[phasenam] # pointer to current phase info
2728                cbox.Add(
2729                    CIFtemplateSelect(self.cifdefs,
2730                                      cpnl,'phase',phasedict['General'],
2731                                      EditCIFDefaults,
2732                                      title,
2733                                      phasenam),
2734                    0,wx.EXPAND|wx.ALIGN_LEFT|wx.ALL)
2735                cpnl.SetSizer(cbox)
2736                if phasedict['General']['Type'] == 'nuclear':
2737                    but = wx.Button(cpnl, wx.ID_ANY,'Edit distance/angle ranges')
2738                    cbox.Add(but,0,wx.ALIGN_LEFT,0)
2739                    cbox.Add((-1,2))
2740                    but.phasedict = self.Phases[phasenam]  # set a pointer to current phase info
2741                    but.Bind(wx.EVT_BUTTON,EditRanges)     # phase bond/angle ranges
2742                    but = wx.Button(cpnl, wx.ID_ANY,'Set distance/angle publication flags')
2743                    but.phase = phasenam  # set a pointer to current phase info
2744                    but.Bind(wx.EVT_BUTTON,SelectDisAglFlags)     # phase bond/angle ranges
2745                    cbox.Add(but,0,wx.ALIGN_LEFT,0)
2746                if self._CellSelectNeeded(phasenam):
2747                    but = wx.Button(cpnl, wx.ID_ANY,'Select cell temperature')
2748                    cbox.Add(but,0,wx.ALIGN_LEFT,0)
2749                    cbox.Add((-1,2))
2750                    but.phase = phasenam  # set a pointer to current phase info
2751                    but.Bind(wx.EVT_BUTTON,SetCellT)
2752                cbox.Add((-1,2))
2753            for i in sorted(self.powderDict.keys()):
2754                G2G.HorizontalLine(cbox,cpnl)
2755                if seqmode:
2756                    hist = self.powderDict[i]
2757                    histblk = self.Histograms[hist]
2758                    title = 'All Powder datasets'
2759                    self.seqSmplParms = {}
2760                    cbox.Add(
2761                        CIFtemplateSelect(self.cifdefs,
2762                                      cpnl,'powder',self.seqSmplParms,
2763                                      EditCIFDefaults,
2764                                      title,
2765                                      histblk["Sample Parameters"]['InstrName']),
2766                        0,wx.EXPAND|wx.ALIGN_LEFT|wx.ALL)
2767                    break
2768                hist = self.powderDict[i]
2769                histblk = self.Histograms[hist]
2770                title = 'Powder dataset '+hist[5:]
2771                cbox.Add(
2772                    CIFtemplateSelect(self.cifdefs,
2773                                      cpnl,'powder',histblk["Sample Parameters"],
2774                                      EditCIFDefaults,
2775                                      title,
2776                                      histblk["Sample Parameters"]['InstrName']),
2777                    0,wx.EXPAND|wx.ALIGN_LEFT|wx.ALL)
2778            for i in sorted(self.xtalDict.keys()):
2779                G2G.HorizontalLine(cbox,cpnl)
2780                hist = self.xtalDict[i]
2781                histblk = self.Histograms[hist]
2782                title = 'Single Xtal dataset '+hist[5:]
2783                cbox.Add(
2784                    CIFtemplateSelect(self.cifdefs,
2785                                      cpnl,'single',histblk["Instrument Parameters"][0],
2786                                      EditCIFDefaults,
2787                                      title,
2788                                      histblk["Instrument Parameters"][0]['InstrName']),
2789                    0,wx.EXPAND|wx.ALIGN_LEFT|wx.ALL)
2790            cpnl.SetSizer(cbox)
2791            cpnl.SetAutoLayout(1)
2792            cpnl.SetupScrolling()
2793            #cpnl.Bind(rw.EVT_RW_LAYOUT_NEEDED, self.OnLayoutNeeded) # needed if sizes change
2794            cpnl.Layout()
2795
2796            vbox.Add(cpnl, 1, wx.ALIGN_LEFT|wx.ALL|wx.EXPAND, 0)
2797            btnsizer = wx.StdDialogButtonSizer()
2798            btn = wx.Button(self.cifdefs, wx.ID_OK, "Create CIF")
2799            btn.SetDefault()
2800            btnsizer.AddButton(btn)
2801            btn = wx.Button(self.cifdefs, wx.ID_CANCEL)
2802            btnsizer.AddButton(btn)
2803            btnsizer.Realize()
2804            vbox.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5)
2805            self.cifdefs.SetSizer(vbox)
2806            if not saveSize:
2807                vbox.Fit(self.cifdefs)
2808            self.cifdefs.Layout()
2809
2810        def OnToggleButton(event):
2811            'Respond to press of ToggleButton in SelectDisAglFlags'
2812            but = event.GetEventObject()
2813            if but.GetValue():
2814                but.DisAglSel[but.key] = True
2815            else:
2816                try:
2817                    del but.DisAglSel[but.key]
2818                except KeyError:
2819                    pass
2820        def keepTrue(event):
2821            event.GetEventObject().SetValue(True)
2822        def keepFalse(event):
2823            event.GetEventObject().SetValue(False)
2824
2825        def SelectDisAglFlags(event):
2826            'Select Distance/Angle use flags for the selected phase'
2827            phasenam = event.GetEventObject().phase
2828            phasedict = self.Phases[phasenam]
2829            SymOpList,offsetList,symOpList,G2oprList,G2opcodes = G2spc.AllOps(phasedict['General']['SGData'])
2830            generalData = phasedict['General']
2831            # create a dict for storing Pub flag for bonds/angles, if needed
2832            if phasedict['General'].get("DisAglHideFlag") is None:
2833                phasedict['General']["DisAglHideFlag"] = {}
2834            DisAngSel = phasedict['General']["DisAglHideFlag"]
2835
2836            cx,ct,cs,cia = phasedict['General']['AtomPtrs']
2837            cn = ct-1
2838            cfrac = cx+3
2839            DisAglData = {}
2840            # create a list of atoms, but skip atoms with zero occupancy
2841            xyz = []
2842            fpfx = str(phasedict['pId'])+'::Afrac:'
2843            for i,atom in enumerate(phasedict['Atoms']):
2844                if self.parmDict.get(fpfx+str(i),atom[cfrac]) == 0.0: continue
2845                xyz.append([i,]+atom[cn:cn+2]+atom[cx:cx+3])
2846            if 'DisAglCtls' not in generalData:
2847                # should not be used, since DisAglDialog should be called
2848                # for all phases before getting here
2849                dlg = G2G.DisAglDialog(
2850                    self.cifdefs,
2851                    {},
2852                    generalData)
2853                if dlg.ShowModal() == wx.ID_OK:
2854                    generalData['DisAglCtls'] = dlg.GetData()
2855                else:
2856                    dlg.Destroy()
2857                    return
2858                dlg.Destroy()
2859            dlg = wx.Dialog(
2860                self.G2frame,
2861                style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
2862            vbox = wx.BoxSizer(wx.VERTICAL)
2863            txt = wx.StaticText(dlg,wx.ID_ANY,'Searching distances for phase '+phasenam
2864                                +'\nPlease wait...')
2865            vbox.Add(txt,0,wx.ALL|wx.EXPAND)
2866            dlg.SetSizer(vbox)
2867            dlg.CenterOnParent()
2868            dlg.Show() # post "please wait"
2869            wx.BeginBusyCursor() # and change cursor
2870
2871            DisAglData['OrigAtoms'] = xyz
2872            DisAglData['TargAtoms'] = xyz
2873            SymOpList,offsetList,symOpList,G2oprList,G2opcodes = G2spc.AllOps(
2874                generalData['SGData'])
2875
2876#            xpandSGdata = generalData['SGData'].copy()
2877#            xpandSGdata.update({'SGOps':symOpList,
2878#                                'SGInv':False,
2879#                                'SGLatt':'P',
2880#                                'SGCen':np.array([[0, 0, 0]]),})
2881#            DisAglData['SGData'] = xpandSGdata
2882            DisAglData['SGData'] = generalData['SGData'].copy()
2883
2884            DisAglData['Cell'] = generalData['Cell'][1:] #+ volume
2885            if 'pId' in phasedict:
2886                DisAglData['pId'] = phasedict['pId']
2887                DisAglData['covData'] = self.OverallParms['Covariance']
2888            try:
2889                AtomLabels,DistArray,AngArray = G2stMn.RetDistAngle(
2890                    generalData['DisAglCtls'],
2891                    DisAglData)
2892            except KeyError:        # inside DistAngle for missing atom types in DisAglCtls
2893                print('**** ERROR - try again but do "Reset" to fill in missing atom types ****')
2894            wx.EndBusyCursor()
2895            txt.SetLabel('Set publication flags for distances and angles in\nphase '+phasenam)
2896            vbox.Add((5,5))
2897            vbox.Add(wx.StaticText(dlg,wx.ID_ANY,
2898                                   'The default is to flag all distances and angles as to be'+
2899                                   '\npublished. Change this by pressing appropriate buttons.'),
2900                     0,wx.ALL|wx.EXPAND)
2901            hbox = wx.BoxSizer(wx.HORIZONTAL)
2902            vbox.Add(hbox)
2903            hbox.Add(wx.StaticText(dlg,wx.ID_ANY,'Button appearance: '))
2904            but = wx.ToggleButton(dlg,wx.ID_ANY,'Publish')
2905            but.Bind(wx.EVT_TOGGLEBUTTON,keepFalse)
2906            hbox.Add(but)
2907            but = wx.ToggleButton(dlg,wx.ID_ANY,"Don't publish")
2908            but.Bind(wx.EVT_TOGGLEBUTTON,keepTrue)
2909            hbox.Add(but)
2910            but.SetValue(True)
2911            G2G.HorizontalLine(vbox,dlg)
2912
2913            cpnl = wxscroll.ScrolledPanel(dlg,size=(400,300))
2914            cbox = wx.BoxSizer(wx.VERTICAL)
2915            for c in sorted(DistArray):
2916                karr = []
2917                UsedCols = {}
2918                cbox.Add(wx.StaticText(cpnl,wx.ID_ANY,
2919                                   'distances to/angles around atom '+AtomLabels[c]))
2920                #dbox = wx.GridBagSizer(hgap=5)
2921                dbox = wx.GridBagSizer()
2922                for i,D in enumerate(DistArray[c]):
2923                    karr.append(tuple(D[0:3]))
2924                    val = "{:.2f}".format(D[3])
2925                    sym = " [{:d} {:d} {:d}]".format(*D[1]) + " #{:d}".format(D[2])
2926                    dbox.Add(wx.StaticText(cpnl,wx.ID_ANY,AtomLabels[D[0]]),
2927                             (i+1,0)
2928                             )
2929                    dbox.Add(wx.StaticText(cpnl,wx.ID_ANY,sym),
2930                             (i+1,1)
2931                             )
2932                    but = wx.ToggleButton(cpnl,wx.ID_ANY,val)
2933                    but.key = (c,karr[-1])
2934                    but.DisAglSel = DisAngSel
2935                    if DisAngSel.get(but.key): but.SetValue(True)
2936                    but.Bind(wx.EVT_TOGGLEBUTTON,OnToggleButton)
2937                    dbox.Add(but,(i+1,2),border=1)
2938                for i,D in enumerate(AngArray[c]):
2939                    val = "{:.1f}".format(D[2][0])
2940                    but = wx.ToggleButton(cpnl,wx.ID_ANY,val)
2941                    but.key = (karr[D[0]],c,karr[D[1]])
2942                    but.DisAglSel = DisAngSel
2943                    if DisAngSel.get(but.key): but.SetValue(True)
2944                    but.Bind(wx.EVT_TOGGLEBUTTON,OnToggleButton)
2945                    dbox.Add(but,(D[0]+1,D[1]+3),border=1)
2946                    UsedCols[D[1]+3] = True
2947                for i,D in enumerate(DistArray[c][:-1]): # label columns that are used
2948                    if UsedCols.get(i+3):
2949                        dbox.Add(wx.StaticText(cpnl,wx.ID_ANY,AtomLabels[D[0]]),
2950                                 (0,i+3),
2951                                 flag=wx.ALIGN_CENTER
2952                                 )
2953                dbox.Add(wx.StaticText(cpnl,wx.ID_ANY,'distance'),
2954                                 (0,2),
2955                                 flag=wx.ALIGN_CENTER
2956                                 )
2957                cbox.Add(dbox)
2958                G2G.HorizontalLine(cbox,cpnl)
2959            cpnl.SetSizer(cbox)
2960            cpnl.SetAutoLayout(1)
2961            cpnl.SetupScrolling()
2962            #cpnl.Bind(rw.EVT_RW_LAYOUT_NEEDED, self.OnLayoutNeeded) # needed if sizes change
2963            cpnl.Layout()
2964
2965            vbox.Add(cpnl, 1, wx.ALIGN_LEFT|wx.ALL|wx.EXPAND, 0)
2966
2967            btnsizer = wx.StdDialogButtonSizer()
2968            btn = wx.Button(dlg, wx.ID_OK, "Done")
2969            btn.SetDefault()
2970            btnsizer.AddButton(btn)
2971            btnsizer.Realize()
2972            vbox.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5)
2973            dlg.SetSizer(vbox)
2974            vbox.Fit(dlg)
2975            dlg.Layout()
2976
2977            dlg.CenterOnParent()
2978            dlg.ShowModal()
2979
2980#==============================================================================
2981####  _Exporter code starts here         ======================================
2982#==============================================================================
2983        # make sure required information is present
2984        self.CIFdate = dt.datetime.strftime(dt.datetime.now(),"%Y-%m-%dT%H:%M")
2985        if not self.CIFname: # Get a name for the CIF. If not defined, use the GPX name (save, if that is needed).
2986            if not self.G2frame.GSASprojectfile:
2987                self.G2frame.OnFileSaveas(None)
2988            if not self.G2frame.GSASprojectfile: return
2989            self.CIFname = os.path.splitext(
2990                os.path.split(self.G2frame.GSASprojectfile)[1]
2991                )[0]
2992            self.CIFname = self.CIFname.replace(' ','')
2993        # replace non-ASCII characters in CIFname with dots
2994        s = ''
2995        for c in self.CIFname:
2996            if ord(c) < 128:
2997                s += c
2998            else:
2999                s += '.'
3000        self.CIFname = s
3001        phasebyhistDict = {} # a cross-reference to phases by histogram -- sequential fits
3002        #=================================================================
3003        # write quick CIFs
3004        #=================================================================
3005        if phaseOnly: #====Phase only CIF ================================
3006            print('Writing CIF output to file '+self.filename)
3007            oneblock = True
3008            self.quickmode = True
3009            self.Write(' ')
3010            self.Write(70*'#')
3011            WriteCIFitem(self.fp, 'data_'+phaseOnly.replace(' ','_'))
3012            #phaseblk = self.Phases[phaseOnly] # pointer to current phase info
3013            # report the phase info
3014            WritePhaseInfo(phaseOnly)
3015            return
3016        elif histOnly: #====Histogram only CIF ================================
3017            print('Writing CIF output to file '+self.filename)
3018            hist = histOnly
3019            #histname = histOnly.replace(' ','')
3020            oneblock = True
3021            self.quickmode = True
3022            self.ifHKLF = False
3023            self.ifPWDR = True
3024            self.Write(' ')
3025            self.Write(70*'#')
3026            #phasenam = self.Phases.keys()[0]
3027            WriteCIFitem(self.fp, 'data_'+self.CIFname)
3028            if hist.startswith("PWDR"):
3029                WritePowderData(hist)
3030            elif hist.startswith("HKLF"):
3031                WriteSingleXtalData(hist)
3032            return
3033        #===============================================================================
3034        # setup for sequential fits here
3035        #===============================================================================
3036        seqmode = False
3037        seqHistList = self.G2frame.testSeqRefineMode()
3038        if seqHistList:
3039            if self.seqData is None:
3040                raise Exception('Use Export/Sequential project for sequential refinements')
3041            phaseWithHist = True   # include the phase in the same block as the histogram
3042            if len(self.Phases) > 1:
3043                phaseWithHist = False  # multiple phases per histogram
3044            seqmode = True
3045        #===============================================================================
3046        # the export process for a full CIF starts here (Sequential too)
3047        #===============================================================================
3048        # load saved CIF author name
3049        self.author = self.OverallParms['Controls'].get("Author",'?').strip()
3050        # initialize dict for Selection of Hist for unit cell reporting
3051        self.OverallParms['Controls']['CellHistSelection'] = self.OverallParms[
3052            'Controls'].get('CellHistSelection',{})
3053        self.CellHistSelection = self.OverallParms['Controls']['CellHistSelection']
3054        # create a dict with refined values and their uncertainties
3055        self.loadParmDict()
3056        # is there anything to export?
3057        if len(self.Phases) == len(self.powderDict) == len(self.xtalDict) == 0:
3058           self.G2frame.ErrorDialog(
3059               'Empty project',
3060               'Project does not contain any data or phases. Are they interconnected?')
3061           return
3062        if self.ExportSelect('ask'): return
3063        if not self.filename:
3064            print('No name supplied')
3065            return
3066        self.OpenFile()
3067       
3068        #if self.ExportSelect('default'): return
3069        # Someday: get restraint & constraint info
3070        #restraintDict = self.OverallParms.get('Restraints',{})
3071        #for i in  self.OverallParms['Constraints']:
3072        #    print i
3073        #    for j in self.OverallParms['Constraints'][i]:
3074        #        print j
3075
3076        self.quickmode = False # full CIF
3077        phasenam = None # include all phases
3078        # Will this require a multiblock CIF?
3079        if len(self.Phases) > 1:
3080            oneblock = False
3081        elif len(self.powderDict) + len(self.xtalDict) > 1:
3082            oneblock = False
3083        else: # one phase, one dataset, Full CIF
3084            oneblock = True
3085
3086        # check there is an instrument name for every histogram
3087        self.ifPWDR = False
3088        self.ifHKLF = False
3089        invalid = 0
3090        key3 = 'InstrName'
3091        for hist in self.Histograms:
3092            if hist.startswith("PWDR"):
3093                self.ifPWDR = True
3094                key2 = "Sample Parameters"
3095                d = self.Histograms[hist][key2]
3096            elif hist.startswith("HKLF"):
3097                self.ifHKLF = True
3098                key2 = "Instrument Parameters"
3099                d = self.Histograms[hist][key2][0]
3100            instrname = d.get(key3)
3101            if instrname is None:
3102                d[key3] = ''
3103                invalid += 1
3104            elif instrname.strip() == '':
3105                invalid += 1
3106            if hist.startswith("PWDR") and seqmode: break
3107        if invalid:
3108            #msg = ""
3109            #if invalid > 3: msg = (
3110            #    "\n\nNote: it may be faster to set the name for\n"
3111            #    "one histogram for each instrument and use the\n"
3112            #    "File/Copy option to duplicate the name"
3113            #    )
3114            if not EditInstNames(): return
3115
3116        # check for a distance-angle range search range for each phase
3117        for phasenam in sorted(self.Phases.keys()):
3118            #i = self.Phases[phasenam]['pId']
3119            phasedict = self.Phases[phasenam] # pointer to current phase info
3120            if 'DisAglCtls' not in phasedict['General']:
3121                dlg = G2G.DisAglDialog(
3122                    self.G2frame,
3123                    {},
3124                    phasedict['General'])
3125                if dlg.ShowModal() == wx.ID_OK:
3126                    phasedict['General']['DisAglCtls'] = dlg.GetData()
3127                else:
3128                    dlg.Destroy()
3129                    return
3130                dlg.Destroy()
3131                   
3132        # check if temperature values & pressure are defaulted
3133        default = 0
3134        for hist in self.Histograms:
3135            if hist.startswith("PWDR"):
3136                key2 = "Sample Parameters"
3137                T = self.Histograms[hist][key2].get('Temperature')
3138                if not T:
3139                    default += 1
3140                elif T == 300:
3141                    default += 1
3142                P = self.Histograms[hist][key2].get('Pressure')
3143                if not P:
3144                    default += 1
3145                elif P == 1:
3146                    default += 1
3147        if default > 0:
3148            dlg = wx.MessageDialog(
3149                self.G2frame,
3150                '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?',
3151                'Check T and P values',
3152                wx.OK|wx.CANCEL)
3153            ret = dlg.ShowModal()
3154            dlg.CenterOnParent()
3155            dlg.Destroy()
3156            if ret != wx.ID_OK: return
3157        if oneblock:
3158            # select a dataset to use (there should only be one set in one block,
3159            # but take whatever comes 1st)
3160            for hist in self.Histograms:
3161                histblk = self.Histograms[hist]
3162                if hist.startswith("PWDR"):
3163                    instnam = histblk["Sample Parameters"]['InstrName']
3164                    break # ignore all but 1st data histogram
3165                elif hist.startswith("HKLF"):
3166                    instnam = histblk["Instrument Parameters"][0]['InstrName']
3167                    break # ignore all but 1st data histogram
3168        # give the user a window to edit CIF contents
3169        if not self.author:
3170            self.author = self.OverallParms['Controls'].get("Author",'?').strip()
3171        if not self.author:
3172            if not EditAuthor(): return
3173        self.ValidateAscii([('Author name',self.author),]) # check for ASCII strings where needed, warn on problems
3174        self.shortauthorname = self.author.replace(',','').replace(' ','')[:20]
3175        self.cifdefs = wx.Dialog(
3176            self.G2frame,
3177            style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
3178        self.cifdefs.G2frame = self.G2frame
3179        self.cifdefs.CenterOnParent()
3180        EditCIFDefaults()
3181        if self.cifdefs.ShowModal() != wx.ID_OK:
3182            self.cifdefs.Destroy()
3183            return
3184        while self.ValidateAscii([('Author name',self.author),
3185                                  ]): # validate a few things as ASCII
3186            if self.cifdefs.ShowModal() != wx.ID_OK:
3187                self.cifdefs.Destroy()
3188                return
3189        self.cifdefs.Destroy()
3190        #======================================================================
3191        #---- Start writing the CIF - single block
3192        #======================================================================
3193        print('Writing CIF output to file '+self.filename+"...")
3194        if self.currentExportType == 'single' or self.currentExportType == 'powder':
3195            #======Data only CIF (powder/xtal) ====================================
3196            hist = self.histnam[0]
3197            self.CIFname = hist[5:40].replace(' ','')
3198            WriteCIFitem(self.fp, 'data_'+self.CIFname)
3199            if hist.startswith("PWDR"):
3200                WritePowderData(hist)
3201            elif hist.startswith("HKLF"):
3202                WriteSingleXtalData(hist)
3203            else:
3204                print ("should not happen")
3205        elif oneblock:
3206            #====Single block, data & phase CIF ===================================
3207            WriteCIFitem(self.fp, 'data_'+self.CIFname)
3208            if phasenam is None: # if not already selected, select the first phase (should be one)
3209                phasenam = self.Phases.keys()[0]
3210            #print 'phasenam',phasenam
3211            #phaseblk = self.Phases[phasenam] # pointer to current phase info
3212            instnam = instnam.replace(' ','')
3213            WriteCIFitem(self.fp, '_pd_block_id',
3214                         str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
3215                         str(self.shortauthorname) + "|" + instnam)
3216            WriteAudit()
3217            writeCIFtemplate(self.OverallParms['Controls'],'publ') # overall (publication) template
3218            WriteOverall()
3219            writeCIFtemplate(self.Phases[phasenam]['General'],'phase',phasenam) # write phase template
3220            # report the phase info
3221            WritePhaseInfo(phasenam,False)
3222            if hist.startswith("PWDR"):  # this is invoked for single-block CIFs
3223                # preferred orientation
3224                SH = FormatSH(phasenam)
3225                MD = FormatHAPpo(phasenam)
3226                if SH and MD:
3227                    WriteCIFitem(self.fp, '_pd_proc_ls_pref_orient_corr', SH + '\n' + MD)
3228                elif SH or MD:
3229                    WriteCIFitem(self.fp, '_pd_proc_ls_pref_orient_corr', SH + MD)
3230                else:
3231                    WriteCIFitem(self.fp, '_pd_proc_ls_pref_orient_corr', 'none')
3232                # report profile, since one-block: include both histogram and phase info (N.B. there is only 1 of each)
3233                WriteCIFitem(self.fp, '_pd_proc_ls_profile_function',
3234                    FormatInstProfile(histblk["Instrument Parameters"],histblk['hId'])
3235                    +'\n'+FormatPhaseProfile(phasenam))
3236
3237                histblk = self.Histograms[hist]["Sample Parameters"]
3238                writeCIFtemplate(histblk,'powder',histblk['InstrName']) # write powder template
3239                WritePowderData(hist)
3240            elif hist.startswith("HKLF"):
3241                histprm = self.Histograms[hist]["Instrument Parameters"][0]
3242                writeCIFtemplate(histprm,'single',histprm['InstrName']) # single crystal template
3243                WriteSingleXtalData(hist)
3244        elif seqHistList:
3245            #---- sequential fit project (multiblock)  ====================
3246            for phasenam in sorted(self.Phases.keys()):
3247                rId = phasedict['ranId']
3248                if rId in self.CellHistSelection: continue
3249                self.CellHistSelection[rId] = self._CellSelectHist(phasenam)
3250            nsteps = 1 + len(self.Phases) + len(self.powderDict) + len(self.xtalDict)
3251            try:
3252                dlg = wx.ProgressDialog('CIF progress','starting',nsteps,parent=self.G2frame)
3253                dlg.CenterOnParent()
3254
3255                # publication info
3256                step = 1
3257                dlg.Update(step,"Exporting overall section")
3258                WriteCIFitem(self.fp, '\ndata_'+self.CIFname+'_publ')
3259                WriteAudit()
3260                WriteCIFitem(self.fp, '_pd_block_id',
3261                             str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
3262                             str(self.shortauthorname) + "|Overall")
3263                writeCIFtemplate(self.OverallParms['Controls'],'publ') #insert the publication template
3264                # ``template_publ.cif`` or a modified version
3265                # overall info
3266                WriteCIFitem(self.fp, 'data_'+str(self.CIFname)+'_overall')
3267                WriteOverall('seq')
3268                hist = self.powderDict[sorted(self.powderDict.keys())[0]]
3269                instnam = self.Histograms[hist]["Sample Parameters"]['InstrName']
3270                writeCIFtemplate(self.seqSmplParms,'powder',instnam) # powder template
3271                instnam = instnam.replace(' ','')
3272                #============================================================
3273                if phaseWithHist:
3274                    WriteCIFitem(self.fp, '# POINTERS TO HISTOGRAM BLOCKS (Phase in histogram block)')
3275                else:
3276                    WriteCIFitem(self.fp, '# POINTERS TO HISTOGRAM BLOCKS (Phases pointer in histogram block)') 
3277                datablockidDict = {} # save block names here
3278                # loop over data blocks
3279                if len(self.powderDict) + len(self.xtalDict) > 1:
3280                    loopprefix = ''
3281                    WriteCIFitem(self.fp, 'loop_   _pd_block_diffractogram_id')
3282                else:
3283                    loopprefix = '_pd_block_diffractogram_id'
3284                for i in sorted(self.powderDict.keys()):
3285                    hist = self.powderDict[i]
3286                    j = self.Histograms[hist]['hId']
3287                    datablockidDict[hist] = (str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
3288                                             str(self.shortauthorname) + "|" +
3289                                             instnam + "_hist_"+str(j))
3290                    WriteCIFitem(self.fp, loopprefix,datablockidDict[hist])
3291                for i in sorted(self.xtalDict.keys()):
3292                    hist = self.xtalDict[i]
3293                    histblk = self.Histograms[hist]
3294                    instnam = histblk["Instrument Parameters"][0]['InstrName']
3295                    instnam = instnam.replace(' ','')
3296                    i = histblk['hId']
3297                    datablockidDict[hist] = (str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
3298                                             str(self.shortauthorname) + "|" +
3299                                             instnam + "_hist_"+str(i))
3300                    WriteCIFitem(self.fp, loopprefix,datablockidDict[hist])
3301                # setup and show sequential results table
3302                tblLabels,tblValues,tblSigs,tblTypes = mkSeqResTable('cif',seqHistList,self.seqData,
3303                                                    self.Phases,self.Histograms,self.Controls)
3304                WriteCIFitem(self.fp, '\n# Sequential results table') # (in case anyone can make sense of it)
3305                WriteCIFitem(self.fp, 'loop_   _gsas_seq_results_col_num _gsas_seq_results_col_label')
3306                for i,lbl in enumerate(tblLabels):
3307                    s = PutInCol(str(i),5)
3308                    if ' ' in lbl:
3309                        s += '"' + lbl + '"'
3310                    else:
3311                        s += lbl
3312                    WriteCIFitem(self.fp,"  "+s)
3313                s = 'loop_ '
3314                linelength = 120
3315                for i in range(len(tblLabels)):
3316                    if len(s) > linelength:
3317                        WriteCIFitem(self.fp,s)
3318                        s = '  '
3319                    s += " _gsas_seq_results_val" + str(i)
3320                WriteCIFitem(self.fp,s)
3321               
3322                for r in range(len(tblValues[0])):
3323                    s = ''
3324                    for c in range(len(tblLabels)):
3325                        if len(s) > linelength:
3326                            WriteCIFitem(self.fp,s)
3327                            s = '  '
3328                        sig = None
3329                        if tblSigs[c] is not None:
3330                            sig = tblSigs[c][r]
3331                           
3332                        if tblValues[c][r] is None:
3333                            if tblTypes[c] == 'int':
3334                                wid = 5
3335                            elif tblTypes[c] == 'str':
3336                                wid = 10
3337                            else:
3338                                wid = 12                               
3339                            s += PutInCol('.',wid)
3340                        elif sig is None and ',' in tblTypes[c]:
3341                            s += PutInCol(
3342                                ('{{:{}.{}f}}'.format(*tblTypes[c].split(','))).format(tblValues[c][r]),12)
3343                        elif tblTypes[c] == 'int':
3344                            s += PutInCol(str(tblValues[c][r]),5)
3345                        elif tblTypes[c] == 'str':
3346                            s += PutInCol(str(tblValues[c][r]),10)
3347                        elif sig is None and ',' in tblTypes[c]:
3348                            s += PutInCol(
3349                                ('{{:{}.{}f}}'.format(*tblTypes[c].split(','))).format(tblValues[c][r]),12)
3350                        elif sig is None and tblTypes[c] == 'float':
3351                            s += PutInCol('{:.6g}'.format(tblValues[c][r]),12)
3352                        elif sig:
3353                            s += PutInCol(G2mth.ValEsd(tblValues[c][r],sig),12)
3354                        else:
3355                            s += PutInCol(str(tblValues[c][r]),15)
3356                    WriteCIFitem(self.fp,s+'\n')
3357
3358                # sample template info & info for all phases
3359
3360                i = sorted(self.powderDict.keys())[0]
3361                hist = self.powderDict[i]
3362                histblk = self.Histograms[hist]
3363                if phaseWithHist:    # include sample info in overall block
3364                    phasenam = list(self.Phases.keys())[0]
3365                    writeCIFtemplate(self.Phases[phasenam]['General'],'phase',phasenam) # write phase template
3366                    WriteSeqOverallPhaseInfo(phasenam,histblk)
3367                else:
3368                    for j,phasenam in enumerate(sorted(self.Phases.keys())):
3369                        pId = self.Phases[phasenam]['pId']
3370                        WriteCIFitem(self.fp, '\n#'+78*'=')
3371                        WriteCIFitem(self.fp, 'data_'+self.CIFname+"_overall_phase"+str(j)+'\n')
3372                        writeCIFtemplate(self.Phases[phasenam]['General'],'phase',phasenam) # write phase template
3373                        WriteSeqOverallPhaseInfo(phasenam,histblk)
3374                       
3375                for i in sorted(self.powderDict.keys()):
3376                    hist = self.powderDict[i]
3377                    hId = self.Histograms[hist]['hId']
3378                    dlg.Update(step,"Exporting "+hist.strip())
3379                    histblk = self.Histograms[hist]
3380                    WriteCIFitem(self.fp, '# Information for histogram '+str(i)+': '+hist)
3381                    WriteCIFitem(self.fp, '\ndata_'+self.CIFname+"_pwd_"+str(i))
3382                    WriteCIFitem(self.fp, '_pd_block_id',datablockidDict[hist])
3383                    if not phaseWithHist:
3384                        WriteCIFitem(self.fp, '\n# POINTERS TO PHASE BLOCKS')
3385                        phaseBlockName = {}
3386
3387                        wtFrSum = 0.
3388                        for j,phasenam in enumerate(sorted(self.Phases.keys())):
3389                            if hist not in self.Phases[phasenam]['Histograms']: continue
3390                            if not self.Phases[phasenam]['Histograms'][hist]['Use']: continue
3391                            phFrac = self.Phases[phasenam]['Histograms'][hist]['Scale'][0]
3392                            phFracKey = str(self.Phases[phasenam]['pId'])+':'+str(hId)+':Scale'
3393                            phFrac = self.seqData[hist]['parmDict'].get(phFracKey,phFrac)
3394                            wtFrSum += phFrac * self.Phases[phasenam]['General']['Mass']
3395                        WriteCIFitem(self.fp, 'loop_ _pd_phase_id _pd_phase_block_id _pd_phase_mass_%')
3396                        for j,phasenam in enumerate(sorted(self.Phases.keys())):
3397                            pId = self.Phases[phasenam]['pId']
3398                            if hist not in self.Phases[phasenam]['Histograms']: continue
3399                            if not self.Phases[phasenam]['Histograms'][hist]['Use']: continue
3400                            if ' ' in phasenam:
3401                                s = PutInCol('"'+phasenam+'"',20)
3402                            else:
3403                                s = PutInCol(phasenam,20)
3404                            phaseBlockName[pId] = datablockidDict[hist]+'_p'+str(j+1)
3405                            phFrac = self.Phases[phasenam]['Histograms'][hist]['Scale'][0]
3406                            phFracKey = str(self.Phases[phasenam]['pId'])+':'+str(hId)+':Scale'
3407                            phFrac = self.seqData[hist]['parmDict'].get(phFracKey,phFrac)
3408                            wtFr = phFrac * self.Phases[phasenam]['General']['Mass'] / wtFrSum
3409                            if phFracKey in self.seqData[hist]['varyList']:
3410                                sig = self.seqData[hist]['sig'][self.seqData[hist]['varyList'].index(phFracKey)]
3411                                sig *= self.Phases[phasenam]['General']['Mass'] / wtFrSum
3412                            elif phFracKey in self.seqData[hist]['depParmDict']:
3413                                sig = self.seqData[hist]['depParmDict'][phFracKey][1]
3414                                sig *= self.Phases[phasenam]['General']['Mass'] / wtFrSum
3415                            else:
3416                                sig = -0.0001
3417                            WriteCIFitem(self.fp, "  "+ s + " " + phaseBlockName[pId] + "  " + G2mth.ValEsd(wtFr,sig))
3418                        PP = FormatInstProfile(histblk["Instrument Parameters"],histblk['hId'])
3419                        PP += '\n'
3420                        WriteCIFitem(self.fp, '_pd_proc_ls_profile_function',PP)
3421               
3422                    WritePowderData(hist,seq=True) # write background, data & reflections, some instrument & sample terms
3423                    WriteCIFitem(self.fp, '\n# PHASE INFO FOR HISTOGRAM '+hist)
3424                    for j,phasenam in enumerate(sorted(self.Phases.keys())):
3425                        pId = self.Phases[phasenam]['pId']
3426                        if hist not in self.Phases[phasenam]['Histograms']: continue
3427                        if not self.Phases[phasenam]['Histograms'][hist]['Use']: continue
3428                        WriteCIFitem(self.fp, '\n# phase info for '+str(phasenam) + ' follows')
3429                        if not phaseWithHist:
3430                            WriteCIFitem(self.fp, 'data_'+self.CIFname+"_hist"+str(i)+"_phase"+str(j))
3431                            WriteCIFitem(self.fp, '_pd_block_id',phaseBlockName[pId])
3432                            WriteCIFitem(self.fp, '')
3433
3434                        WriteSeqPhaseVals(phasenam,self.Phases[phasenam],pId,hist)
3435
3436                        # preferred orientation & profile terms
3437                        if self.ifPWDR:
3438                            #SH = FormatSH(phasenam)     # TODO: needs to use seqData
3439                            #MD = FormatHAPpo(phasenam)  # TODO: switch to seqData
3440                            #if SH and MD:
3441                            #    WriteCIFitem(self.fp, '_pd_proc_ls_pref_orient_corr', SH + '\n' + MD)
3442                            #elif SH or MD:
3443                            #    WriteCIFitem(self.fp, '_pd_proc_ls_pref_orient_corr', SH + MD)
3444                            #else:
3445                            #    WriteCIFitem(self.fp, '_pd_proc_ls_pref_orient_corr', 'none')
3446                            # report sample profile terms for all histograms with current phase
3447                            if phaseWithHist:
3448                                PP = FormatInstProfile(histblk["Instrument Parameters"],histblk['hId'])
3449                                PP += '\n'
3450                            else:
3451                                PP = ''
3452                            PP += FormatPhaseProfile(phasenam,hist)
3453                            WriteCIFitem(self.fp, '\n_pd_proc_ls_profile_function',PP)
3454            finally:
3455                dlg.Destroy()
3456        else:
3457            #---- multiblock: multiple phases and/or histograms ====================
3458            for phasenam in sorted(self.Phases.keys()):
3459                rId = phasedict['ranId']
3460                if rId in self.CellHistSelection: continue
3461                self.CellHistSelection[rId] = self._CellSelectHist(phasenam)
3462            nsteps = 1 + len(self.Phases) + len(self.powderDict) + len(self.xtalDict)
3463            try:
3464                dlg = wx.ProgressDialog('CIF progress','starting',nsteps,parent=self.G2frame)
3465                dlg.CenterOnParent()
3466
3467                # publication info
3468                step = 1
3469                dlg.Update(step,"Exporting overall section")
3470                WriteCIFitem(self.fp, '\ndata_'+self.CIFname+'_publ')
3471                WriteAudit()
3472                WriteCIFitem(self.fp, '_pd_block_id',
3473                             str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
3474                             str(self.shortauthorname) + "|Overall")
3475                writeCIFtemplate(self.OverallParms['Controls'],'publ') #insert the publication template
3476                # ``template_publ.cif`` or a modified version
3477                # overall info
3478                WriteCIFitem(self.fp, 'data_'+str(self.CIFname)+'_overall')
3479                WriteOverall()
3480                #============================================================
3481                WriteCIFitem(self.fp, '# POINTERS TO PHASE AND HISTOGRAM BLOCKS')
3482                datablockidDict = {} # save block names here -- N.B. check for conflicts between phase & hist names (unlikely!)
3483                # loop over phase blocks
3484                if len(self.Phases) > 1:
3485                    loopprefix = ''
3486                    WriteCIFitem(self.fp, 'loop_   _pd_phase_block_id')
3487                else:
3488                    loopprefix = '_pd_phase_block_id'
3489
3490                for phasenam in sorted(self.Phases.keys()):
3491                    i = self.Phases[phasenam]['pId']
3492                    datablockidDict[phasenam] = (str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
3493                                 'phase_'+ str(i) + '|' + str(self.shortauthorname))
3494                    WriteCIFitem(self.fp, loopprefix,datablockidDict[phasenam])
3495                # loop over data blocks
3496                if len(self.powderDict) + len(self.xtalDict) > 1:
3497                    loopprefix = ''
3498                    WriteCIFitem(self.fp, 'loop_   _pd_block_diffractogram_id')
3499                else:
3500                    loopprefix = '_pd_block_diffractogram_id'
3501                for i in sorted(self.powderDict.keys()):
3502                    hist = self.powderDict[i]
3503                    histblk = self.Histograms[hist]
3504                    instnam = histblk["Sample Parameters"]['InstrName']
3505                    instnam = instnam.replace(' ','')
3506                    j = histblk['hId']
3507                    datablockidDict[hist] = (str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
3508                                             str(self.shortauthorname) + "|" +
3509                                             instnam + "_hist_"+str(j))
3510                    WriteCIFitem(self.fp, loopprefix,datablockidDict[hist])
3511                for i in sorted(self.xtalDict.keys()):
3512                    hist = self.xtalDict[i]
3513                    histblk = self.Histograms[hist]
3514                    instnam = histblk["Instrument Parameters"][0]['InstrName']
3515                    instnam = instnam.replace(' ','')
3516                    i = histblk['hId']
3517                    datablockidDict[hist] = (str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
3518                                             str(self.shortauthorname) + "|" +
3519                                             instnam + "_hist_"+str(i))
3520                    WriteCIFitem(self.fp, loopprefix,datablockidDict[hist])
3521                #============================================================
3522                # loop over phases, exporting them
3523                for j,phasenam in enumerate(sorted(self.Phases.keys())):
3524                    step += 1
3525                    dlg.Update(step,"Exporting phase "+phasenam+' (#'+str(j+1)+')')
3526                    i = self.Phases[phasenam]['pId']
3527                    WriteCIFitem(self.fp, '\ndata_'+self.CIFname+"_phase_"+str(i))
3528                    WriteCIFitem(self.fp, '# Information for phase '+str(i))
3529                    WriteCIFitem(self.fp, '_pd_block_id',datablockidDict[phasenam])
3530                    # report the phase
3531                    writeCIFtemplate(self.Phases[phasenam]['General'],'phase',phasenam) # write phase template
3532                    WritePhaseInfo(phasenam,False,False)
3533                    # preferred orientation
3534                    if self.ifPWDR:
3535                        SH = FormatSH(phasenam)
3536                        MD = FormatHAPpo(phasenam)
3537                        if SH and MD:
3538                            WriteCIFitem(self.fp, '_pd_proc_ls_pref_orient_corr', SH + '\n' + MD)
3539                        elif SH or MD:
3540                            WriteCIFitem(self.fp, '_pd_proc_ls_pref_orient_corr', SH + MD)
3541                        else:
3542                            WriteCIFitem(self.fp, '_pd_proc_ls_pref_orient_corr', 'none')
3543                    # report sample profile terms for all histograms with current phase
3544                    PP = FormatPhaseProfile(phasenam)
3545                    if PP:
3546                        WriteCIFitem(self.fp, '_pd_proc_ls_profile_function',PP)
3547                    self.ShowHstrainCells(phasenam,datablockidDict)
3548
3549                #============================================================
3550                # loop over histograms, exporting them
3551                # first, get atoms across all phases
3552                uniqueAtoms = []
3553                for phasenam in self.Phases:
3554                    cx,ct,cs,cia = self.Phases[phasenam]['General']['AtomPtrs']
3555                    for line in self.Phases[phasenam]['Atoms']:
3556                        atype = line[ct].strip()
3557                        if atype.find('-') != -1: atype = atype.split('-')[0]
3558                        if atype.find('+') != -1: atype = atype.split('+')[0]
3559                        atype = atype[0].upper()+atype[1:2].lower() # force case conversion
3560                        if atype == "D" or atype == "D": atype = "H"
3561                        if atype not in uniqueAtoms:
3562                            uniqueAtoms.append(atype)
3563
3564                for i in sorted(self.powderDict.keys()):
3565                    hist = self.powderDict[i]
3566                    histblk = self.Histograms[hist]
3567                    if hist.startswith("PWDR"):
3568                        step += 1
3569                        dlg.Update(step,"Exporting "+hist.strip())
3570                        WriteCIFitem(self.fp, '\ndata_'+self.CIFname+"_pwd_"+str(i))
3571                        WriteCIFitem(self.fp, '# Information for histogram '+str(i)+': '+hist)
3572                        #instnam = histblk["Sample Parameters"]['InstrName']
3573                        # report instrumental profile terms
3574                        WriteCIFitem(self.fp, '_pd_proc_ls_profile_function',
3575                            FormatInstProfile(histblk["Instrument Parameters"],histblk['hId']))
3576                        WriteCIFitem(self.fp, '_pd_block_id',datablockidDict[hist])
3577                        histprm = self.Histograms[hist]["Sample Parameters"]
3578                        writeCIFtemplate(histprm,'powder',histprm['InstrName']) # powder template
3579
3580                        # get xray wavelength and compute & write f' & f''
3581                        lam = None
3582                        if 'X' in histblk['Instrument Parameters'][0]['Type'][0]:
3583                            for k in ('Lam','Lam1'):
3584                                if k in histblk['Instrument Parameters'][0]:
3585                                    lam = histblk['Instrument Parameters'][0][k][0]
3586                                    break
3587                        if lam:
3588                            keV = 12.397639/lam
3589                            WriteCIFitem(self.fp,'loop_')
3590                            for item in ('_atom_type_symbol','_atom_type_scat_dispersion_real',
3591                                             '_atom_type_scat_dispersion_imag','_atom_type_scat_dispersion_source'):
3592                                WriteCIFitem(self.fp,'     '+item)
3593                            for elem in HillSortElements(uniqueAtoms):
3594                                s = '  '
3595                                s += PutInCol(elem,4)
3596                                Orbs = G2el.GetXsectionCoeff(elem)
3597                                FP,FPP,Mu = G2el.FPcalc(Orbs, keV)
3598                                s += {:8.3f}{:8.3f}   https://subversion.xray.aps.anl.gov/pyGSAS/trunk/atmdata.py'.format(FP,FPP)
3599                                WriteCIFitem(self.fp,s.rstrip())
3600                            WriteCIFitem(self.fp,'')
3601                        WritePowderData(hist)
3602                for i in sorted(self.xtalDict.keys()):
3603                    hist = self.xtalDict[i]
3604                    histblk = self.Histograms[hist]
3605                    if hist.startswith("HKLF"):
3606                        step += 1
3607                        dlg.Update(step,"Exporting "+hist.strip())
3608                        WriteCIFitem(self.fp, '\ndata_'+self.CIFname+"_sx_"+str(i))
3609                        #instnam = histblk["Instrument Parameters"][0]['InstrName']
3610                        WriteCIFitem(self.fp, '# Information for histogram '+str(i)+': '+hist)
3611                        WriteCIFitem(self.fp, '_pd_block_id',datablockidDict[hist])
3612                        histprm = self.Histograms[hist]["Instrument Parameters"][0]
3613                        writeCIFtemplate(histprm,'single',histprm['InstrName']) # single crystal template
3614                        WriteSingleXtalData(hist)
3615            finally:
3616                dlg.Destroy()
3617
3618        WriteCIFitem(self.fp, '#--' + 15*'eof--' + '#')
3619        print("...export completed")
3620        print('file '+self.fullpath)
3621        # end of CIF export
3622
3623class ExportProjectCIF(ExportCIF):
3624    '''Used to create a CIF of an entire project
3625
3626    :param wx.Frame G2frame: reference to main GSAS-II frame
3627    '''
3628    def __init__(self,G2frame):
3629        ExportCIF.__init__(self,
3630            G2frame=G2frame,
3631            formatName = 'Full CIF',
3632            extension='.cif',
3633            longFormatName = 'Export project as CIF'
3634            )
3635        self.exporttype = ['project']
3636
3637    def Exporter(self,event=None,seqData=None,Controls=None):
3638        self.seqData = seqData
3639        self.Controls = Controls
3640        self.InitExport(event)
3641        # load all of the tree into a set of dicts
3642        self.loadTree()
3643        self._Exporter(event=event)
3644        self.CloseFile()
3645
3646class ExportPhaseCIF(ExportCIF):
3647    '''Used to create a simple CIF with one phase. Uses exact same code as
3648    :class:`ExportCIF` except that `phaseOnly` is set for the Exporter
3649    Shows up in menu as Quick CIF.
3650
3651    :param wx.Frame G2frame: reference to main GSAS-II frame
3652    '''
3653    def __init__(self,G2frame):
3654        ExportCIF.__init__(self,
3655            G2frame=G2frame,
3656            formatName = 'Quick CIF',
3657            extension='.cif',
3658            longFormatName = 'Export one phase in CIF'
3659            )
3660        self.exporttype = ['phase']
3661        # CIF-specific items
3662        self.author = ''
3663
3664    def Exporter(self,event=None):
3665        # get a phase and file name
3666        # the export process starts here
3667        self.InitExport(event)
3668        # load all of the tree into a set of dicts
3669        self.loadTree()
3670        # create a dict with refined values and their uncertainties
3671        self.loadParmDict()
3672        self.multiple = True
3673        self.currentExportType = 'phase'
3674        if self.ExportSelect('ask'): return
3675        self.OpenFile()
3676        for name in self.phasenam:
3677            self._Exporter(event=event,phaseOnly=name)  #TODO: repeat for magnetic phase
3678        self.CloseFile()
3679
3680    def Writer(self,hist,phasenam,mode='w'):
3681        # set the project file name
3682        self.CIFname = os.path.splitext(
3683            os.path.split(self.G2frame.GSASprojectfile)[1]
3684            )[0]+'_'+phasenam+'_'+hist
3685        self.CIFname = self.CIFname.replace(' ','')
3686        self.OpenFile(mode=mode)
3687        self._Exporter(phaseOnly=phasenam)
3688        self.CloseFile()
3689
3690class ExportPwdrCIF(ExportCIF):
3691    '''Used to create a simple CIF containing diffraction data only. Uses exact same code as
3692    :class:`ExportCIF` except that `histOnly` is set for the Exporter
3693    Shows up in menu as Quick CIF.
3694
3695    :param wx.Frame G2frame: reference to main GSAS-II frame
3696    '''
3697    def __init__(self,G2frame):
3698        ExportCIF.__init__(self,
3699            G2frame=G2frame,
3700            formatName = 'Data-only CIF',
3701            extension='.cif',
3702            longFormatName = 'Export data as CIF'
3703            )
3704        if G2frame is None: raise AttributeError('CIF export requires data tree') # prevent use from Scriptable
3705        self.exporttype = ['powder']
3706        # CIF-specific items
3707        self.author = ''
3708
3709    def Exporter(self,event=None):
3710        self.InitExport(event)
3711        # load all of the tree into a set of dicts
3712        self.currentExportType = None
3713        self.loadTree()
3714        self.currentExportType = 'powder'
3715        # create a dict with refined values and their uncertainties
3716        self.loadParmDict()
3717        self.multiple = False
3718        if self.ExportSelect( # set export parameters
3719            AskFile='ask' # get a file name/directory to save in
3720            ): return
3721        self.OpenFile()
3722        self._Exporter(event=event,histOnly=self.histnam[0])
3723
3724    def Writer(self,hist,mode='w'):
3725        '''Used for histogram CIF export of a sequential fit.
3726        '''
3727        # set the project file name
3728        self.CIFname = os.path.splitext(
3729            os.path.split(self.G2frame.GSASprojectfile)[1]
3730            )[0]+'_'+hist
3731        self.CIFname = self.CIFname.replace(' ','')
3732        self.OpenFile(mode=mode)
3733        self._Exporter(histOnly=hist)
3734        if mode == 'w':
3735            print('CIF written to file '+self.fullpath)
3736        self.CloseFile()
3737
3738class ExportHKLCIF(ExportCIF):
3739    '''Used to create a simple CIF containing diffraction data only. Uses exact same code as
3740    :class:`ExportCIF` except that `histOnly` is set for the Exporter
3741    Shows up in menu as Quick CIF.
3742
3743    :param wx.Frame G2frame: reference to main GSAS-II frame
3744    '''
3745    def __init__(self,G2frame):
3746        ExportCIF.__init__(self,
3747            G2frame=G2frame,
3748            formatName = 'Data-only CIF',
3749            extension='.cif',
3750            longFormatName = 'Export data as CIF'
3751            )
3752        self.exporttype = ['single']
3753        # CIF-specific items
3754        self.author = ''
3755
3756    def Exporter(self,event=None):
3757        self.InitExport(event)
3758        # load all of the tree into a set of dicts
3759        self.currentExportType = None
3760        self.loadTree()
3761        self.currentExportType = 'single'
3762        # create a dict with refined values and their uncertainties
3763        self.loadParmDict()
3764        self.multiple = False
3765        if self.ExportSelect( # set export parameters
3766            AskFile='ask' # get a file name/directory to save in
3767            ): return
3768        self.OpenFile()
3769        self._Exporter(event=event,histOnly=self.histnam[0])
3770
3771#===============================================================================
3772# misc CIF utilities
3773#===============================================================================
3774def PickleCIFdict(fil):
3775    '''Loads a CIF dictionary, cherry picks out the items needed
3776    by local code and sticks them into a python dict and writes
3777    that dict out as a pickle file for later reuse.
3778    If the write fails a warning message is printed,
3779    but no exception occurs.
3780
3781    :param str fil: file name of CIF dictionary, will usually end
3782      in .dic
3783    :returns: the dict with the definitions
3784    '''
3785    import CifFile as cif # PyCifRW from James Hester
3786    cifdic = {}
3787    try:
3788        fp = open(fil,'r')             # patch: open file to avoid windows bug
3789        dictobj = cif.CifDic(fp)
3790        fp.close()
3791    except IOError:
3792        dictobj = cif.CifDic(fil)
3793    if DEBUG: print('loaded '+fil)
3794    for item in dictobj.keys():
3795        cifdic[item] = {}
3796        for j in (
3797            '_definition','_type',
3798            '_enumeration',
3799            '_enumeration_detail',
3800            '_enumeration_range'):
3801            if dictobj[item].get(j):
3802                cifdic[item][j] = dictobj[item][j]
3803    try:
3804        fil = os.path.splitext(fil)[0]+'.cpickle'
3805        fp = open(fil,'wb')
3806        pickle.dump(cifdic,fp)
3807        fp.close()
3808        if DEBUG: print('wrote '+fil)
3809    except:
3810        print ('Unable to write '+fil)
3811    return cifdic
3812
3813def LoadCIFdic():
3814    '''Create a composite core+powder CIF lookup dict containing
3815    information about all items in the CIF dictionaries, loading
3816    pickled files if possible. The routine looks for files
3817    named cif_core.cpickle and cif_pd.cpickle in every
3818    directory in the path and if they are not found, files
3819    cif_core.dic and/or cif_pd.dic are read.
3820
3821    :returns: the dict with the definitions
3822    '''
3823    cifdic = {}
3824    for ftyp in "cif_core","cif_pd":
3825        for loc in sys.path:
3826            fil = os.path.join(loc,ftyp+".cpickle")
3827            if not os.path.exists(fil): continue
3828            fp = open(fil,'rb')
3829            try:
3830                cifdic.update(pickle.load(fp))
3831                if DEBUG: print('reloaded '+fil)
3832                break
3833            finally:
3834                fp.close()
3835        else:
3836            for loc in sys.path:
3837                fil = os.path.join(loc,ftyp+".dic")
3838                if not os.path.exists(fil): continue
3839                #try:
3840                if True:
3841                    cifdic.update(PickleCIFdict(fil))
3842                    break
3843                #except:
3844                #    pass
3845            else:
3846                print('Could not load '+ftyp+' dictionary')
3847    return cifdic
3848
3849class CIFdefHelp(wx.Button):
3850    '''Create a help button that displays help information on
3851    the current data item
3852
3853    :param parent: the panel which will be the parent of the button
3854    :param str msg: the help text to be displayed
3855    :param wx.Dialog helpwin: Frame for CIF editing dialog
3856    :param wx.TextCtrl helptxt: TextCtrl where help text is placed
3857    '''
3858    def __init__(self,parent,msg,helpwin,helptxt):
3859        wx.Button.__init__(self,parent,wx.ID_HELP)
3860        self.Bind(wx.EVT_BUTTON,self._onPress)
3861        self.msg=msg
3862        self.parent = parent
3863        self.helpwin = helpwin
3864        self.helptxt = helptxt
3865    def _onPress(self,event):
3866        'Respond to a button press by displaying the requested text'
3867        try:
3868            ww,wh = self.helpwin.GetSize()
3869            ow,oh = self.helptxt.GetSize()
3870            self.helptxt.SetLabel(self.msg)
3871            self.helptxt.Wrap(ww-10)
3872            w,h = self.helptxt.GetSize()
3873            if h > oh: # resize the help area if needed, but avoid changing width
3874                self.helptxt.SetMinSize((ww,h))
3875                self.helpwin.GetSizer().Fit(self.helpwin)
3876        except: # error posting help, ignore
3877            return
3878
3879def CIF2dict(cf):
3880    '''copy the contents of a CIF out from a PyCifRW block object
3881    into a dict
3882
3883    :returns: cifblk, loopstructure where cifblk is a dict with
3884      CIF items and loopstructure is a list of lists that defines
3885      which items are in which loops.
3886    '''
3887    blk = cf.keys()[0] # assume templates are a single CIF block, use the 1st
3888    try:
3889        loopstructure = cf[blk].loopnames()[:] # copy over the list of loop contents
3890    except AttributeError:
3891        loopstructure = [j[:] for j in cf[blk].loops.values()] # method replaced?
3892    dblk = {}
3893    for item in cf[blk].keys(): # make a copy of all the items in the block
3894        dblk[item] = cf[blk][item]
3895    return dblk,loopstructure
3896
3897def dict2CIF(dblk,loopstructure,blockname='Template'):
3898    '''Create a PyCifRW CIF object containing a single CIF
3899    block object from a dict and loop structure list.
3900
3901    :param dblk: a dict containing values for each CIF item
3902    :param list loopstructure: a list of lists containing the contents of
3903      each loop, as an example::
3904
3905         [ ["_a","_b"], ["_c"], ["_d_1","_d_2","_d_3"]]
3906
3907      this describes a CIF with this type of structure::
3908
3909        loop_ _a _b <a1> <b1> <a2> ...
3910        loop_ _c <c1> <c2>...
3911        loop _d_1 _d_2 _d_3 ...
3912
3913      Note that the values for each looped CIF item, such as _a,
3914      are contained in a list, for example as cifblk["_a"]
3915
3916    :param str blockname: an optional name for the CIF block.
3917      Defaults to 'Template'
3918
3919    :returns: the newly created PyCifRW CIF object
3920    '''
3921
3922    import CifFile as cif # PyCifRW from James Hester
3923    # compile a 'list' of items in loops
3924    loopnames = set()
3925    for i in loopstructure:
3926        loopnames |= set(i)
3927    # create a new block
3928    newblk = cif.CifBlock()
3929    # add the looped items
3930    for keys in loopstructure:
3931        vals = []
3932        for key in keys:
3933            vals.append(dblk[key])
3934        newblk.AddCifItem(([keys],[vals]))
3935    # add the non-looped items
3936    for item in dblk:
3937        if item in loopnames: continue
3938        newblk[item] = dblk[item]
3939    # create a CIF and add the block
3940    newcf = cif.CifFile()
3941    newcf[blockname] = newblk
3942    return newcf
3943
3944
3945class EditCIFtemplate(wx.Dialog):
3946    '''Create a dialog for editing a CIF template. The edited information is
3947    placed in cifblk. If the CIF is saved as a file, the name of that file
3948    is saved as ``self.newfile``.
3949
3950    :param wx.Frame parent: parent frame or None
3951    :param cifblk: dict or PyCifRW block containing values for each CIF item
3952    :param list loopstructure: a list of lists containing the contents of
3953      each loop, as an example::
3954
3955         [ ["_a","_b"], ["_c"], ["_d_1","_d_2","_d_3"]]
3956
3957      this describes a CIF with this type of structure::
3958
3959        loop_ _a _b <a1> <b1> <a2> ...
3960        loop_ _c <c1> <c2>...
3961        loop _d_1 _d_2 _d_3 ...
3962
3963      Note that the values for each looped CIF item, such as _a,
3964      are contained in a list, for example as cifblk["_a"]
3965
3966    :param str defaultname: specifies the default file name to be used for
3967      saving the CIF.
3968    '''
3969    def __init__(self,parent,cifblk,loopstructure,defaultname):
3970        OKbuttons = []
3971        self.cifblk = cifblk
3972        self.loopstructure = loopstructure
3973        self.newfile = None
3974        self.defaultname = defaultname
3975        self.G2frame = parent.G2frame
3976        global CIFdic  # once this is loaded, keep it around
3977        if CIFdic is None:
3978            CIFdic = LoadCIFdic()
3979        wx.Dialog.__init__(self,parent,style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
3980
3981        # define widgets that will be needed during panel creation
3982        self.helptxt = wx.StaticText(self,wx.ID_ANY,"")
3983        savebtn = wx.Button(self, wx.ID_CLOSE, "Save as template")
3984        OKbuttons.append(savebtn)
3985        savebtn.Bind(wx.EVT_BUTTON,self._onSave)
3986        OKbtn = wx.Button(self, wx.ID_OK, "Use")
3987        OKbtn.Bind(wx.EVT_BUTTON, lambda x: self.EndModal(wx.ID_OK))
3988        OKbtn.SetDefault()
3989        OKbuttons.append(OKbtn)
3990
3991        self.SetTitle('Edit items in CIF template')
3992        vbox = wx.BoxSizer(wx.VERTICAL)
3993        cpnl = EditCIFpanel(self,cifblk,loopstructure,CIFdic,OKbuttons,size=(300,300))
3994        vbox.Add(cpnl, 1, wx.ALIGN_LEFT|wx.ALL|wx.EXPAND, 0)
3995        G2G.HorizontalLine(vbox,self)
3996        vbox.Add(self.helptxt, 0, wx.EXPAND|wx.ALL, 5)
3997        G2G.HorizontalLine(vbox,self)
3998        btnsizer = wx.BoxSizer(wx.HORIZONTAL)
3999        btn = wx.Button(self, wx.ID_CANCEL)
4000        btnsizer.Add(btn,0,wx.ALIGN_CENTER|wx.ALL)
4001        btnsizer.Add(savebtn,0,wx.ALIGN_CENTER|wx.ALL)
4002        btnsizer.Add(OKbtn,0,wx.ALIGN_CENTER|wx.ALL)
4003        vbox.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5)
4004        self.SetSizer(vbox)
4005        vbox.Fit(self)
4006    def Post(self):
4007        '''Display the dialog
4008
4009        :returns: True unless Cancel has been pressed.
4010        '''
4011        return (self.ShowModal() == wx.ID_OK)
4012    def _onSave(self,event):
4013        'Save CIF entries in a template file'
4014        pth = G2G.GetExportPath(self.G2frame)
4015        dlg = wx.FileDialog(
4016            self, message="Save as CIF template",
4017            defaultDir=pth,
4018            defaultFile=self.defaultname,
4019            wildcard="CIF (*.cif)|*.cif",
4020            style=wx.FD_SAVE)
4021        val = (dlg.ShowModal() == wx.ID_OK)
4022        fil = dlg.GetPath()
4023        dlg.Destroy()
4024        if val: # ignore a Cancel button
4025            fil = os.path.splitext(fil)[0]+'.cif' # force extension
4026            fp = open(fil,'w')
4027            newcf = dict2CIF(self.cifblk,self.loopstructure)
4028            fp.write(newcf.WriteOut())
4029            fp.close()
4030            self.newfile = fil
4031            self.EndModal(wx.ID_OK)
4032
4033class EditCIFpanel(wxscroll.ScrolledPanel):
4034    '''Creates a scrolled panel for editing CIF template items
4035
4036    :param wx.Frame parent: parent frame where panel will be placed
4037    :param cifblk: dict or PyCifRW block containing values for each CIF item
4038    :param list loopstructure: a list of lists containing the contents of
4039      each loop, as an example::
4040
4041         [ ["_a","_b"], ["_c"], ["_d_1","_d_2","_d_3"]]
4042
4043      this describes a CIF with this type of structure::
4044
4045        loop_ _a _b <a1> <b1> <a2> ...
4046        loop_ _c <c1> <c2>...
4047        loop _d_1 _d_2 _d_3 ...
4048
4049      Note that the values for each looped CIF item, such as _a,
4050      are contained in a list, for example as cifblk["_a"]
4051
4052    :param dict cifdic: optional CIF dictionary definitions
4053    :param list OKbuttons: A list of wx.Button objects that should
4054      be disabled when information in the CIF is invalid
4055    :param (other): optional keyword parameters for wx.ScrolledPanel
4056    '''
4057    def __init__(self, parent, cifblk, loopstructure, cifdic={}, OKbuttons=[], **kw):
4058        self.parent = parent
4059        wxscroll.ScrolledPanel.__init__(self, parent, wx.ID_ANY, **kw)
4060        self.vbox = None
4061        self.AddDict = None
4062        self.cifdic = cifdic
4063        self.cifblk = cifblk
4064        self.loops = loopstructure
4065        self.parent = parent
4066        self.LayoutCalled = False
4067        self.parentOKbuttons = OKbuttons
4068        self.ValidatedControlsList = []
4069        self.G2frame = parent.G2frame
4070        self.height = G2G.getTextSize('?')[1]
4071        #print('height is ',self.height)
4072        self._fill()
4073    def _fill(self):
4074        'Fill the scrolled panel with widgets for each CIF item'
4075        wx.BeginBusyCursor()
4076        self.AddDict = {}
4077        self.ValidatedControlsList = []
4078        # delete any only contents
4079        if self.vbox:
4080            if 'phoenix' in wx.version():
4081                self.vbox.Clear(True)
4082            else:
4083                self.vbox.DeleteWindows()
4084            self.vbox = None
4085            self.Update()
4086        vbox = wx.BoxSizer(wx.VERTICAL)
4087        self.vbox = vbox
4088        # compile a 'list' of items in loops
4089        loopnames = set()
4090        for i in self.loops:
4091            loopnames |= set(i)
4092        # post the looped CIF items
4093        for lnum,lp in enumerate(self.loops):
4094            hbox = wx.BoxSizer(wx.HORIZONTAL)
4095            hbox.Add(wx.StaticText(self,wx.ID_ANY,'Loop '+str(lnum+1)))
4096            vbox.Add(hbox)
4097            but = wx.Button(self,wx.ID_ANY,"Add row")
4098            self.AddDict[but]=lnum
4099
4100            hbox.Add(but)
4101            but.Bind(wx.EVT_BUTTON,self.OnAddRow)
4102            fbox = wx.GridBagSizer(0, 0)
4103            vbox.Add(fbox)
4104            rows = 0
4105            for i,item in enumerate(lp):
4106                txt = wx.StaticText(self,wx.ID_ANY,item+"  ")
4107                fbox.Add(txt,(0,i+1))
4108                for j,val in enumerate(self.cifblk[item]):
4109                    ent = self.CIFEntryWidget(self.cifblk[item],j,item)
4110                    #fbox.Add(ent,(j+2,i+1),flag=wx.EXPAND|wx.ALL)
4111                    fbox.Add(ent,(j+1,i+1),flag=wx.EXPAND|wx.ALL)
4112                if self.cifdic.get(item):
4113                    df = self.cifdic[item].get('_definition')
4114                    if df:
4115                        try:
4116                            txt.SetToolTip(G2IO.trim(df))
4117                        except:
4118                            txt.SetToolTipString(G2IO.trim(df))
4119                        but = CIFdefHelp(self,
4120                                         "Definition for "+item+":\n\n"+G2IO.trim(df),
4121                                         self.parent,
4122                                         self.parent.helptxt)
4123                        fbox.Add(but,(j+2,i+1),flag=wx.ALIGN_CENTER)
4124                rows = max(rows,len(self.cifblk[item]))
4125            for i in range(rows):
4126                txt = wx.StaticText(self,wx.ID_ANY,str(i+1))
4127                fbox.Add(txt,(i+1,0))
4128            line = wx.StaticLine(self,wx.ID_ANY, size=(-1,3), style=wx.LI_HORIZONTAL)
4129            vbox.Add(line, 0, wx.EXPAND|wx.ALL, 10)
4130
4131        # post the non-looped CIF items
4132        for item in sorted(self.cifblk.keys()):
4133            if item not in loopnames:
4134                hbox = wx.BoxSizer(wx.HORIZONTAL)
4135                vbox.Add(hbox)
4136                txt = wx.StaticText(self,wx.ID_ANY,item)
4137                hbox.Add(txt)
4138                ent = self.CIFEntryWidget(self.cifblk,item,item)
4139                hbox.Add(ent)
4140                if self.cifdic.get(item):
4141                    df = self.cifdic[item].get('_definition')
4142                    if df:
4143                        try:
4144                            txt.SetToolTip(G2IO.trim(df))
4145                        except:
4146                            txt.SetToolTipString(G2IO.trim(df))
4147                        but = CIFdefHelp(self,
4148                                         "Definition for "+item+":\n\n"+G2IO.trim(df),
4149                                         self.parent,
4150                                         self.parent.helptxt)
4151                        hbox.Add(but,0,wx.ALL,2)
4152        self.SetSizer(vbox)
4153        #vbox.Fit(self.parent)
4154        self.SetAutoLayout(1)
4155        self.SetupScrolling()
4156        self.Bind(rw.EVT_RW_LAYOUT_NEEDED, self.OnLayoutNeeded)
4157        self.Layout()
4158        wx.EndBusyCursor()
4159    def OnLayoutNeeded(self,event):
4160        '''Called when an update of the panel layout is needed. Calls
4161        self.DoLayout after the current operations are complete using
4162        CallAfter. This is called only once, according to flag
4163        self.LayoutCalled, which is cleared in self.DoLayout.
4164        '''
4165        if self.LayoutCalled: return # call already queued
4166        wx.CallAfter(self.DoLayout) # queue a call
4167        self.LayoutCalled = True
4168    def DoLayout(self):
4169        '''Update the Layout and scroll bars for the Panel. Clears
4170        self.LayoutCalled so that next change to panel can
4171        request a new update
4172        '''
4173        wx.BeginBusyCursor()
4174        self.Layout()
4175        self.SetupScrolling()
4176        wx.EndBusyCursor()
4177        self.LayoutCalled = False
4178    def OnAddRow(self,event):
4179        'add a row to a loop'
4180        lnum = self.AddDict.get(event.GetEventObject())
4181        if lnum is None: return
4182        for item in self.loops[lnum]:
4183            self.cifblk[item].append('?')
4184        self._fill()
4185
4186    def ControlOKButton(self,setvalue):
4187        '''Enable or Disable the OK button(s) for the dialog. Note that this is
4188        passed into the ValidatedTxtCtrl for use by validators.
4189
4190        :param bool setvalue: if True, all entries in the dialog are
4191          checked for validity. The first invalid control triggers
4192          disabling of buttons.
4193          If False then the OK button(s) are disabled with no checking
4194          of the invalid flag for each control.
4195        '''
4196        if setvalue: # turn button on, do only if all controls show as valid
4197            for ctrl in self.ValidatedControlsList:
4198                if ctrl.invalid:
4199                    for btn in self.parentOKbuttons:
4200                        btn.Disable()
4201                    return
4202            else:
4203                for btn in self.parentOKbuttons:
4204                    btn.Enable()
4205        else:
4206            for btn in self.parentOKbuttons:
4207                btn.Disable()
4208
4209    def CIFEntryWidget(self,dct,item,dataname):
4210        '''Create an entry widget for a CIF item. Use a validated entry for numb values
4211        where int is required when limits are integers and floats otherwise.
4212        At present this does not allow entry of the special CIF values of "." and "?" for
4213        numerical values and highlights them as invalid.
4214        Use a selection widget when there are specific enumerated values for a string.
4215        '''
4216        if self.cifdic.get(dataname):
4217            if self.cifdic[dataname].get('_enumeration'):
4218                values = ['?']+self.cifdic[dataname]['_enumeration']
4219                choices = ['undefined']
4220                for i in self.cifdic[dataname].get('_enumeration_detail',values):
4221                    choices.append(G2IO.trim(i))
4222                ent = G2G.EnumSelector(self, dct, item, choices, values, size=(200, -1))
4223                return ent
4224            if self.cifdic[dataname].get('_type') == 'numb':
4225                mn = None
4226                mx = None
4227                hint = int
4228                if self.cifdic[dataname].get('_enumeration_range'):
4229                    rng = self.cifdic[dataname]['_enumeration_range'].split(':')
4230                    if '.' in rng[0] or '.' in rng[1]: hint = float
4231                    if rng[0]: mn = hint(rng[0])
4232                    if rng[1]: mx = hint(rng[1])
4233                    ent = G2G.ValidatedTxtCtrl(
4234                        self,dct,item,typeHint=hint,xmin=mn,xmax=mx,
4235                        CIFinput=True,ASCIIonly=True,
4236                        OKcontrol=self.ControlOKButton)
4237                    self.ValidatedControlsList.append(ent)
4238                    return ent
4239        rw1 = rw.ResizeWidget(self)
4240        ent = G2G.ValidatedTxtCtrl(
4241            rw1,dct,item,size=(100, self.height+5),
4242            style=wx.TE_MULTILINE|wx.TE_PROCESS_ENTER,
4243            CIFinput=True,ASCIIonly=True,
4244            OKcontrol=self.ControlOKButton)
4245        self.ValidatedControlsList.append(ent)
4246        return rw1
4247
4248class CIFtemplateSelect(wx.BoxSizer):
4249    '''Create a set of buttons to show, select and edit a CIF template
4250
4251    :param frame: wx.Frame object of parent
4252    :param panel: wx.Panel object where widgets should be placed
4253    :param str tmplate: one of 'publ', 'phase', or 'instrument' to determine
4254      the type of template
4255    :param dict G2dict: GSAS-II dict where CIF should be placed. The key
4256      "CIF_template" will be used to store either a list or a string.
4257      If a list, it will contain a dict and a list defining loops. If
4258      an str, it will contain a file name.
4259    :param function repaint: reference to a routine to be called to repaint
4260      the frame after a change has been made
4261    :param str title: A line of text to show at the top of the window
4262    :param str defaultname: specifies the default file name to be used for
4263      saving the CIF.
4264    '''
4265    def __init__(self,frame,panel,tmplate,G2dict, repaint, title, defaultname=''):
4266        def _onResetTemplate(event):
4267            self.CIF = None
4268            self.dict["CIF_template"] = resetTemplate
4269            wx.CallAfter(self.repaint)
4270        wx.BoxSizer.__init__(self,wx.VERTICAL)
4271        self.cifdefs = frame
4272        self.dict = G2dict
4273        self.repaint = repaint
4274        self.G2frame = frame.G2frame
4275        templateDefName = 'template_'+tmplate+'.cif'
4276        if defaultname:
4277            self.defaultname = G2obj.StripUnicode(defaultname)
4278            self.defaultname = re.sub(r'[^a-zA-Z0-9_-]','',self.defaultname)
4279            self.defaultname = tmplate + "_" + self.defaultname + ".cif"
4280        else:
4281            self.defaultname = ''
4282
4283        txt = wx.StaticText(panel,wx.ID_ANY,title)
4284        self.Add(txt,0,wx.ALIGN_CENTER)
4285        # change font on title
4286        txtfnt = txt.GetFont()
4287        txtfnt.SetWeight(wx.BOLD)
4288        txtfnt.SetPointSize(2+txtfnt.GetPointSize())
4289        txt.SetFont(txtfnt)
4290        self.Add((-1,3))
4291
4292        # find default name for template
4293        resetTemplate = None
4294        localTemplate = None
4295        for pth in sys.path:           # -- search with default name
4296            fil = os.path.join(pth,templateDefName)
4297            if os.path.exists(fil):
4298                resetTemplate = fil
4299                break
4300        if not resetTemplate:    # this should not happen!
4301            print("Default CIF template file",templateDefName,
4302                          'not found in path!\nProblem with GSAS-II installation?')
4303        for pth in [os.getcwd()]+sys.path: # -- search with name based on hist/phase
4304            fil = os.path.join(pth,self.defaultname)
4305            if os.path.exists(fil) and self.defaultname:
4306                localTemplate = fil
4307                break
4308
4309        if G2dict.get("CIF_template") == localTemplate and localTemplate:
4310            self.CIF = localTemplate
4311            CIFtxt = "Customized template: "+os.path.split(self.CIF)[1]
4312        elif G2dict.get("CIF_template") == resetTemplate and resetTemplate:
4313            self.CIF = resetTemplate
4314            CIFtxt = "Default template: "+os.path.split(self.CIF)[1]
4315        elif not G2dict.get("CIF_template"): # empty or None
4316            if localTemplate:
4317                G2dict["CIF_template"] = self.CIF = localTemplate
4318                CIFtxt = "Customized template: "+os.path.split(self.CIF)[1]
4319            elif resetTemplate:
4320                G2dict["CIF_template"] = self.CIF = resetTemplate
4321                CIFtxt = "Default template: "+os.path.split(self.CIF)[1]
4322            else:
4323                G2dict["CIF_template"] = self.CIF = None
4324                CIFtxt = "none (Template not found!)"
4325        elif type(G2dict["CIF_template"]) is not list and type(
4326                G2dict["CIF_template"]) is not tuple:
4327            if not os.path.exists(G2dict["CIF_template"]):
4328                print("Warning: saved template file,",
4329                          os.path.abspath(G2dict["CIF_template"]),
4330                          ' not found!\nWas this file moved or deleted?')
4331                self.CIF = None
4332                CIFtxt = "none! (file not found)"
4333                if resetTemplate:
4334                    G2dict["CIF_template"] = None
4335                    wx.CallLater(100,self.repaint)
4336                    return
4337            else:
4338                CIFtxt = "Edited template: "+os.path.split(G2dict["CIF_template"])[1]
4339                if GSASIIpath.GetConfigValue('debug'):
4340                    print('Template file found',os.path.abspath(G2dict["CIF_template"]))
4341        else:
4342            self.CIF = G2dict["CIF_template"]
4343            CIFtxt = "Customized template is reloaded"
4344        # show template source
4345        self.Add(wx.StaticText(panel,wx.ID_ANY,CIFtxt))
4346        # show str, button to select file; button to edit (if CIF defined)
4347        but = wx.Button(panel,wx.ID_ANY,"Select Template File")
4348        but.Bind(wx.EVT_BUTTON,self._onGetTemplateFile)
4349        hbox =  wx.BoxSizer(wx.HORIZONTAL)
4350        hbox.Add(but,0,0,2)
4351        but = wx.Button(panel,wx.ID_ANY,"Edit Template")
4352        but.Bind(wx.EVT_BUTTON,self._onEditTemplateContents)
4353        if self.CIF is None: but.Disable() # nothing to edit!
4354        if resetTemplate and not CIFtxt.startswith('Default'):
4355            hbox.Add(but,0,0,2)
4356            but = wx.Button(panel,wx.ID_ANY,"Reset to default template")
4357            but.Bind(wx.EVT_BUTTON,_onResetTemplate)
4358        hbox.Add(but,0,0,2)
4359        self.Add(hbox)
4360    def _onGetTemplateFile(self,event):
4361        'select a template file'
4362        pth = G2G.GetImportPath(self.G2frame)
4363        if not pth: pth = '.'
4364        dlg = wx.FileDialog(
4365            self.cifdefs, message="Read CIF template file",
4366            defaultDir=pth,
4367            defaultFile=self.defaultname,
4368            wildcard="CIF (*.cif)|*.cif",
4369            style=wx.FD_OPEN)
4370        ret = dlg.ShowModal()
4371        fil = dlg.GetPath()
4372        dlg.Destroy()
4373        if ret == wx.ID_OK:
4374            cf = G2obj.ReadCIF(fil)
4375            if len(cf.keys()) == 0:
4376                raise Exception("No CIF data_ blocks found")
4377            if len(cf.keys()) != 1:
4378                raise Exception('Error, CIF Template has more than one block: '+fil)
4379            self.dict["CIF_template"] = fil
4380            wx.CallAfter(self.repaint)
4381
4382    def _onEditTemplateContents(self,event):
4383        'Called to edit the contents of a CIF template'
4384        if type(self.CIF) is list or  type(self.CIF) is tuple:
4385            dblk,loopstructure = copy.deepcopy(self.CIF) # don't modify original
4386        else:
4387            cf = G2obj.ReadCIF(self.CIF)
4388            dblk,loopstructure = CIF2dict(cf)
4389        dlg = EditCIFtemplate(self.cifdefs,dblk,loopstructure,self.defaultname)
4390        val = dlg.Post()
4391        if val:
4392            if dlg.newfile: # results saved in file
4393                self.dict["CIF_template"] = dlg.newfile
4394            else:
4395                self.dict["CIF_template"] = [dlg.cifblk,dlg.loopstructure]
4396            wx.CallAfter(self.repaint) #EditCIFDefaults() # note that this does a dlg.Destroy()
4397        else:
4398            dlg.Destroy()
4399
4400#===============================================================================
4401# end of misc CIF utilities
4402#===============================================================================
Note: See TracBrowser for help on using the repository browser.