source: trunk/exports/G2cif.py @ 1068

Last change on this file since 1068 was 1068, checked in by toby, 8 years ago

nearing end of CIF export

  • Property svn:eol-style set to native
File size: 99.7 KB
Line 
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3#G2cif
4########### SVN repository information ###################
5# $Date: 2013-07-22 20:57:37 -0500 (Mon, 22 Jul 2013) $
6# $Author: toby $
7# $Revision: 1006 $
8# $URL: https://subversion.xray.aps.anl.gov/pyGSAS/trunk/exports/G2cif.py $
9# $Id: G2cif.py 1006 2013-07-23 01:57:37Z toby $
10########### SVN repository information ###################
11'''Code to export a GSAS-II project as a CIF
12The heavy lifting is done in method export
13'''
14
15# TODO: set bond pub flags?
16
17import datetime as dt
18import os.path
19import sys
20import numpy as np
21import cPickle
22import copy
23import re
24import wx
25import wx.lib.scrolledpanel as wxscroll
26import wx.lib.resizewidget as rw
27import GSASIIpath
28GSASIIpath.SetVersionNumber("$Revision: 1006 $")
29import GSASIIIO as G2IO
30#reload(G2IO)
31import GSASIIgrid as G2gd
32reload(G2gd)
33import GSASIIstrIO as G2stIO
34#reload(G2stIO)
35#import GSASIImapvars as G2mv
36import GSASIImath as G2mth
37#reload(G2mth)
38import GSASIIlattice as G2lat
39import GSASIIspc as G2spc
40#reload(G2spc)
41import GSASIIphsGUI as G2pg
42#reload(G2pg)
43import GSASIIstrMain as G2stMn
44#reload(G2stMn)
45
46DEBUG = False    #True to skip printing of reflection/powder profile lists
47
48CIFdic = None
49
50def getCallerDocString(): # for development
51    "Return the calling function's doc string"
52    import inspect as ins
53    for item in ins.stack()[1][0].f_code.co_consts:
54        if type(item) is str:
55            return item
56    else:
57        return '?'
58
59#===============================================================================
60# misc CIF utilities
61#===============================================================================
62def PickleCIFdict(fil):
63    '''Loads a CIF dictionary, cherry picks out the items needed
64    by local code and sticks them into a python dict and writes
65    that dict out as a cPickle file for later reuse.
66    If the write fails a warning message is printed,
67    but no exception occurs.
68
69    :param str fil: file name of CIF dictionary, will usually end
70      in .dic
71    :returns: the dict with the definitions 
72    '''
73    import CifFile as cif # PyCifRW from James Hester
74    cifdic = {}
75    dictobj = cif.CifDic(fil)
76    if DEBUG: print('loaded '+str(fil))
77    for item in dictobj.keys():
78        cifdic[item] = {}
79        for j in (
80            '_definition','_type',
81            '_enumeration',
82            '_enumeration_detail',
83            '_enumeration_range'):
84            if dictobj[item].get(j):
85                cifdic[item][j] = dictobj[item][j]
86    try:
87        fil = os.path.splitext(fil)[0]+'.cpickle'
88        fp = open(fil,'w')
89        cPickle.dump(cifdic,fp)
90        fp.close()
91        if DEBUG: print('wrote '+str(fil))
92    except:
93        print ('Unable to write '+str(fil))
94    return cifdic
95
96def LoadCIFdic():
97    '''Create a composite core+powder CIF lookup dict containing
98    information about all items in the CIF dictionaries, loading
99    pickled files if possible. The routine looks for files
100    named cif_core.cpickle and cif_pd.cpickle in every
101    directory in the path and if they are not found, files
102    cif_core.dic and/or cif_pd.dic are read.
103
104    :returns: the dict with the definitions 
105    '''
106    cifdic = {}
107    for ftyp in "cif_core","cif_pd":
108        for loc in sys.path:
109            fil = os.path.join(loc,ftyp+".cpickle")
110            if not os.path.exists(fil): continue
111            fp = open(fil,'r')
112            try:
113                cifdic.update(cPickle.load(fp))
114                if DEBUG: print('reloaded '+str(fil))
115                break
116            finally:
117                fp.close()
118        else:
119            for loc in sys.path:
120                fil = os.path.join(loc,ftyp+".dic")
121                if not os.path.exists(fil): continue
122                #try:
123                if True:
124                    cifdic.update(PickleCIFdict(fil))
125                    break
126                #except:
127                #    pass
128            else:
129                print('Could not load '+ftyp+' dictionary')
130    return cifdic
131
132class CIFdefHelp(wx.Button):
133    '''Create a help button that displays help information on
134    the current data item
135
136    :param parent: the panel which will be the parent of the button
137    :param str msg: the help text to be displayed
138    :param wx.Dialog helpwin: Frame for CIF editing dialog
139    :param wx.TextCtrl helptxt: TextCtrl where help text is placed
140    '''
141    def __init__(self,parent,msg,helpwin,helptxt):
142        wx.Button.__init__(self,parent,wx.ID_HELP)
143        self.Bind(wx.EVT_BUTTON,self._onPress)
144        self.msg=msg
145        self.parent = parent
146        #self.helpwin = self.parent.helpwin
147        self.helpwin = helpwin
148        self.helptxt = helptxt
149    def _onPress(self,event):
150        'Respond to a button press by displaying the requested text'
151        try:
152            #helptxt = self.helptxt
153            ow,oh = self.helptxt.GetSize()
154            self.helptxt.SetLabel(self.msg)
155            w,h = self.helptxt.GetSize()
156            if h > oh:
157                self.helpwin.GetSizer().Fit(self.helpwin)
158        except: # error posting help, ignore
159            return
160
161def CIF2dict(cf):
162    '''copy the contents of a CIF out from a PyCifRW block object
163    into a dict
164
165    :returns: cifblk, loopstructure where cifblk is a dict with
166      CIF items and loopstructure is a list of lists that defines
167      which items are in which loops.
168    '''
169    blk = cf.keys()[0] # assume templates are a single CIF block, use the 1st
170    loopstructure = cf[blk].loopnames()[:] # copy over the list of loop contents
171    dblk = {}
172    for item in cf[blk].keys(): # make a copy of all the items in the block
173        dblk[item] = cf[blk][item]
174    return dblk,loopstructure
175
176def dict2CIF(dblk,loopstructure,blockname='Template'):
177    '''Create a PyCifRW CIF object containing a single CIF
178    block object from a dict and loop structure list.
179
180    :param dblk: a dict containing values for each CIF item
181    :param list loopstructure: a list of lists containing the contents of
182      each loop, as an example::
183
184         [ ["_a","_b"], ["_c"], ["_d_1","_d_2","_d_3"]]
185
186      this describes a CIF with this type of structure::
187
188        loop_ _a _b <a1> <b1> <a2> ...
189        loop_ _c <c1> <c2>...
190        loop _d_1 _d_2 _d_3 ...
191
192      Note that the values for each looped CIF item, such as _a,
193      are contained in a list, for example as cifblk["_a"]
194
195    :param str blockname: an optional name for the CIF block.
196      Defaults to 'Template'
197
198    :returns: the newly created PyCifRW CIF object
199    '''
200
201    import CifFile as cif # PyCifRW from James Hester
202    # compile a 'list' of items in loops
203    loopnames = set()
204    for i in loopstructure:
205        loopnames |= set(i)
206    # create a new block
207    newblk = cif.CifBlock()
208    # add the looped items
209    for keys in loopstructure:
210        vals = []
211        for key in keys:
212            vals.append(dblk[key])
213        newblk.AddCifItem(([keys],[vals]))
214    # add the non-looped items
215    for item in dblk:
216        if item in loopnames: continue
217        newblk[item] = dblk[item]
218    # create a CIF and add the block
219    newcf = cif.CifFile()
220    newcf[blockname] = newblk   
221    return newcf
222
223
224class EditCIFtemplate(wx.Dialog):
225    '''Create a dialog for editing a CIF template. The edited information is
226    placed in cifblk. If the CIF is saved as a file, the name of that file
227    is saved as ``self.newfile``.
228   
229    :param wx.Frame parent: parent frame or None
230    :param cifblk: dict or PyCifRW block containing values for each CIF item
231    :param list loopstructure: a list of lists containing the contents of
232      each loop, as an example::
233
234         [ ["_a","_b"], ["_c"], ["_d_1","_d_2","_d_3"]]
235
236      this describes a CIF with this type of structure::
237
238        loop_ _a _b <a1> <b1> <a2> ...
239        loop_ _c <c1> <c2>...
240        loop _d_1 _d_2 _d_3 ...
241
242      Note that the values for each looped CIF item, such as _a,
243      are contained in a list, for example as cifblk["_a"]
244     
245    :param str defaultname: specifies the default file name to be used for
246      saving the CIF.
247    '''
248    def __init__(self,parent,cifblk,loopstructure,defaultname):
249        OKbuttons = []
250        self.cifblk = cifblk
251        self.loopstructure = loopstructure
252        self.newfile = None
253        self.defaultname = defaultname       
254        global CIFdic  # once this is loaded, keep it around
255        if CIFdic is None:
256            CIFdic = LoadCIFdic()
257        wx.Dialog.__init__(self,parent,style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
258
259        # define widgets that will be needed during panel creation
260        self.helptxt = wx.StaticText(self,wx.ID_ANY,"")
261        savebtn = wx.Button(self, wx.ID_CLOSE, "Save as template")
262        OKbuttons.append(savebtn)
263        savebtn.Bind(wx.EVT_BUTTON,self._onSave)
264        OKbtn = wx.Button(self, wx.ID_OK, "Use")
265        OKbtn.SetDefault()
266        OKbuttons.append(OKbtn)
267
268        self.SetTitle('Edit items in CIF template')
269        vbox = wx.BoxSizer(wx.VERTICAL)
270        cpnl = EditCIFpanel(self,cifblk,loopstructure,CIFdic,OKbuttons,size=(300,300))
271        vbox.Add(cpnl, 1, wx.ALIGN_LEFT|wx.ALL|wx.EXPAND, 0)
272        G2gd.HorizontalLine(vbox,self)
273        vbox.Add(self.helptxt, 0, wx.EXPAND|wx.ALL, 5)
274        G2gd.HorizontalLine(vbox,self)
275        btnsizer = wx.BoxSizer(wx.HORIZONTAL)
276        btn = wx.Button(self, wx.ID_CANCEL)
277        btnsizer.Add(btn,0,wx.ALIGN_CENTER|wx.ALL)
278        btnsizer.Add(savebtn,0,wx.ALIGN_CENTER|wx.ALL)
279        btnsizer.Add(OKbtn,0,wx.ALIGN_CENTER|wx.ALL)
280        vbox.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5)
281        self.SetSizer(vbox)
282        vbox.Fit(self)
283    def Post(self):
284        '''Display the dialog
285       
286        :returns: True unless Cancel has been pressed.
287        '''
288        return (self.ShowModal() == wx.ID_OK)
289    def _onSave(self,event):
290        'Save CIF entries in a template file'
291        dlg = wx.FileDialog(
292            self, message="Save as CIF template",
293            defaultDir=os.getcwd(),
294            defaultFile=self.defaultname,
295            wildcard="CIF (*.cif)|*.cif",
296            style=wx.SAVE | wx.CHANGE_DIR
297            )
298        val = (dlg.ShowModal() == wx.ID_OK)
299        fil = dlg.GetPath()
300        dlg.Destroy()
301        if val: # ignore a Cancel button
302            fil = os.path.splitext(fil)[0]+'.cif' # force extension
303            fp = open(fil,'w')
304            newcf = dict2CIF(self.cifblk,self.loopstructure)
305            fp.write(newcf.WriteOut())
306            fp.close()
307            self.newfile = fil
308            self.EndModal(wx.ID_OK)
309
310class EditCIFpanel(wxscroll.ScrolledPanel):
311    '''Creates a scrolled panel for editing CIF template items
312
313    :param wx.Frame parent: parent frame where panel will be placed
314    :param cifblk: dict or PyCifRW block containing values for each CIF item
315    :param list loopstructure: a list of lists containing the contents of
316      each loop, as an example::
317
318         [ ["_a","_b"], ["_c"], ["_d_1","_d_2","_d_3"]]
319
320      this describes a CIF with this type of structure::
321
322        loop_ _a _b <a1> <b1> <a2> ...
323        loop_ _c <c1> <c2>...
324        loop _d_1 _d_2 _d_3 ...
325
326      Note that the values for each looped CIF item, such as _a,
327      are contained in a list, for example as cifblk["_a"]
328
329    :param dict cifdic: optional CIF dictionary definitions
330    :param list OKbuttons: A list of wx.Button objects that should
331      be disabled when information in the CIF is invalid
332    :param (other): optional keyword parameters for wx.ScrolledPanel
333    '''
334    def __init__(self, parent, cifblk, loopstructure, cifdic={}, OKbuttons=[], **kw):
335        self.parent = parent
336        wxscroll.ScrolledPanel.__init__(self, parent, wx.ID_ANY, **kw)
337        self.vbox = None
338        self.AddDict = None
339        self.cifdic = cifdic
340        self.cifblk = cifblk
341        self.loops = loopstructure
342        self.parent = parent
343        self.LayoutCalled = False
344        self.parentOKbuttons = OKbuttons
345        self.ValidatedControlsList = []
346        self._fill()
347    def _fill(self):
348        'Fill the scrolled panel with widgets for each CIF item'
349        wx.BeginBusyCursor()
350        self.AddDict = {}
351        self.ValidatedControlsList = []
352        # delete any only contents
353        if self.vbox:
354            self.vbox.DeleteWindows()
355            self.vbox = None
356            self.Update()
357        vbox = wx.BoxSizer(wx.VERTICAL)
358        self.vbox = vbox
359        # compile a 'list' of items in loops
360        loopnames = set()
361        for i in self.loops:
362            loopnames |= set(i)
363        # post the looped CIF items
364        for lnum,lp in enumerate(self.loops):
365            hbox = wx.BoxSizer(wx.HORIZONTAL)
366            hbox.Add(wx.StaticText(self,wx.ID_ANY,'Loop '+str(lnum+1)))
367            vbox.Add(hbox)
368            but = wx.Button(self,wx.ID_ANY,"Add row")
369            self.AddDict[but]=lnum
370           
371            hbox.Add(but)           
372            but.Bind(wx.EVT_BUTTON,self.OnAddRow)
373            fbox = wx.GridBagSizer(0, 0)
374            vbox.Add(fbox)
375            rows = 0
376            for i,item in enumerate(lp):
377                txt = wx.StaticText(self,wx.ID_ANY,item+"  ")
378                fbox.Add(txt,(0,i+1))
379                if self.cifdic.get(item):
380                    df = self.cifdic[item].get('_definition')
381                    if df:
382                        txt.SetToolTipString(G2IO.trim(df))
383                        but = CIFdefHelp(self,
384                                         "Definition for "+item+":\n\n"+G2IO.trim(df),
385                                         self.parent,
386                                         self.parent.helptxt)
387                        fbox.Add(but,(1,i+1),flag=wx.ALIGN_CENTER)
388                for j,val in enumerate(self.cifblk[item]):
389                    ent = self.CIFEntryWidget(self.cifblk[item],j,item)
390                    fbox.Add(ent,(j+2,i+1),flag=wx.EXPAND|wx.ALL)
391                rows = max(rows,len(self.cifblk[item]))
392            for i in range(rows):
393                txt = wx.StaticText(self,wx.ID_ANY,str(i+1))
394                fbox.Add(txt,(i+2,0))
395            line = wx.StaticLine(self,wx.ID_ANY, size=(-1,3), style=wx.LI_HORIZONTAL)
396            vbox.Add(line, 0, wx.EXPAND|wx.ALIGN_CENTER|wx.ALL, 10)
397               
398        # post the non-looped CIF items
399        for item in sorted(self.cifblk.keys()):
400            if item not in loopnames:
401                hbox = wx.BoxSizer(wx.HORIZONTAL)
402                vbox.Add(hbox)
403                txt = wx.StaticText(self,wx.ID_ANY,item)
404                hbox.Add(txt)
405                if self.cifdic.get(item):
406                    df = self.cifdic[item].get('_definition')
407                    if df:
408                        txt.SetToolTipString(G2IO.trim(df))
409                        but = CIFdefHelp(self,
410                                         "Definition for "+item+":\n\n"+G2IO.trim(df),
411                                         self.parent,
412                                         self.parent.helptxt)
413                        hbox.Add(but,0,wx.ALL,2)
414                ent = self.CIFEntryWidget(self.cifblk,item,item)
415                hbox.Add(ent)
416        self.SetSizer(vbox)
417        #vbox.Fit(self.parent)
418        self.SetAutoLayout(1)
419        self.SetupScrolling()
420        self.Bind(rw.EVT_RW_LAYOUT_NEEDED, self.OnLayoutNeeded)
421        self.Layout()
422        wx.EndBusyCursor()
423    def OnLayoutNeeded(self,event):
424        '''Called when an update of the panel layout is needed. Calls
425        self.DoLayout after the current operations are complete using
426        CallAfter. This is called only once, according to flag
427        self.LayoutCalled, which is cleared in self.DoLayout.
428        '''
429        if self.LayoutCalled: return # call already queued
430        wx.CallAfter(self.DoLayout) # queue a call
431        self.LayoutCalled = True
432    def DoLayout(self):
433        '''Update the Layout and scroll bars for the Panel. Clears
434        self.LayoutCalled so that next change to panel can
435        request a new update
436        '''
437        wx.BeginBusyCursor()
438        self.Layout()
439        self.SetupScrolling()
440        wx.EndBusyCursor()
441        self.LayoutCalled = False
442    def OnAddRow(self,event):
443        'add a row to a loop'
444        lnum = self.AddDict.get(event.GetEventObject())
445        if lnum is None: return
446        for item in self.loops[lnum]:
447            self.cifblk[item].append('?')
448        self._fill()
449
450    def ControlOKButton(self,setvalue):
451        '''Enable or Disable the OK button(s) for the dialog. Note that this is
452        passed into the ValidatedTxtCtrl for use by validators.
453
454        :param bool setvalue: if True, all entries in the dialog are
455          checked for validity. The first invalid control triggers
456          disabling of buttons.
457          If False then the OK button(s) are disabled with no checking
458          of the invalid flag for each control.
459        '''
460        if setvalue: # turn button on, do only if all controls show as valid
461            for ctrl in self.ValidatedControlsList:
462                if ctrl.invalid:
463                    for btn in self.parentOKbuttons:
464                        btn.Disable()
465                    return
466            else:
467                for btn in self.parentOKbuttons:
468                    btn.Enable()
469        else:
470            for btn in self.parentOKbuttons:
471                btn.Disable()
472       
473    def CIFEntryWidget(self,dct,item,dataname):
474        '''Create an entry widget for a CIF item. Use a validated entry for numb values
475        where int is required when limits are integers and floats otherwise.
476        At present this does not allow entry of the special CIF values of "." and "?" for
477        numerical values and highlights them as invalid.
478        Use a selection widget when there are specific enumerated values for a string.       
479        '''
480        if self.cifdic.get(dataname):
481            if self.cifdic[dataname].get('_enumeration'):
482                values = ['?']+self.cifdic[dataname]['_enumeration']
483                choices = ['undefined']
484                for i in self.cifdic[dataname].get('_enumeration_detail',values):
485                    choices.append(G2IO.trim(i))
486                ent = G2gd.EnumSelector(self, dct, item, choices, values, size=(200, -1))
487                return ent
488            if self.cifdic[dataname].get('_type') == 'numb':
489                mn = None
490                mx = None
491                hint = int
492                if self.cifdic[dataname].get('_enumeration_range'):
493                    rng = self.cifdic[dataname]['_enumeration_range'].split(':')
494                    if '.' in rng[0] or '.' in rng[1]: hint = float
495                    if rng[0]: mn = hint(rng[0])
496                    if rng[1]: mx = hint(rng[1])
497                    ent = G2gd.ValidatedTxtCtrl(
498                        self,dct,item,typeHint=hint,min=mn,max=mx,
499                        CIFinput=True,
500                        OKcontrol=self.ControlOKButton)
501                    self.ValidatedControlsList.append(ent)
502                    return ent
503        rw1 = rw.ResizeWidget(self)
504        ent = G2gd.ValidatedTxtCtrl(
505            rw1,dct,item,size=(100, 20),
506            style=wx.TE_MULTILINE|wx.TE_PROCESS_ENTER,
507            CIFinput=True,
508            OKcontrol=self.ControlOKButton)
509        self.ValidatedControlsList.append(ent)
510        return rw1
511
512class CIFtemplateSelect(wx.BoxSizer):
513    '''Create a set of buttons to show, select and edit a CIF template
514   
515    :param frame: wx.Frame object of parent
516    :param panel: wx.Panel object where widgets should be placed
517    :param str tmplate: one of 'publ', 'phase', or 'instrument' to determine
518      the type of template
519    :param dict G2dict: GSAS-II dict where CIF should be placed. The key
520      "CIF_template" will be used to store either a list or a string.
521      If a list, it will contain a dict and a list defining loops. If
522      an str, it will contain a file name.   
523    :param function repaint: reference to a routine to be called to repaint
524      the frame after a change has been made
525    :param str title: A line of text to show at the top of the window
526    :param str defaultname: specifies the default file name to be used for
527      saving the CIF.
528    '''
529    def __init__(self,frame,panel,tmplate,G2dict, repaint, title, defaultname=''):
530        wx.BoxSizer.__init__(self,wx.VERTICAL)
531        self.cifdefs = frame
532        self.dict = G2dict
533        self.repaint = repaint
534        templateDefName = 'template_'+tmplate+'.cif'
535        self.CIF = G2dict.get("CIF_template")
536        if defaultname:
537            self.defaultname = defaultname.encode('ascii','replace').strip().replace(' ','_')
538            self.defaultname = re.sub(r'[^a-zA-Z0-9_-]','',self.defaultname)
539            self.defaultname = tmplate + "_" + self.defaultname + ".cif"
540        else:
541            self.defaultname = ''
542           
543        txt = wx.StaticText(panel,wx.ID_ANY,title)
544        self.Add(txt,0,wx.ALIGN_CENTER)
545        # change font on title
546        txtfnt = txt.GetFont()
547        txtfnt.SetWeight(wx.BOLD)
548        txtfnt.SetPointSize(2+txtfnt.GetPointSize())
549        txt.SetFont(txtfnt)
550        self.Add((-1,3))
551
552        if not self.CIF: # empty or None
553            for pth in [os.getcwd()]+sys.path:
554                fil = os.path.join(pth,self.defaultname)
555                if os.path.exists(fil) and self.defaultname:
556                    self.CIF = fil
557                    CIFtxt = "Template: "+self.defaultname
558                    break
559            else:
560                for pth in sys.path:
561                    fil = os.path.join(pth,templateDefName)
562                    if os.path.exists(fil):
563                        self.CIF = fil
564                        CIFtxt = "Template: "+templateDefName
565                        break
566                else:
567                    print(self.CIF+' not found in path!')
568                    self.CIF = None
569                    CIFtxt = "none! (No template found)"
570        elif type(self.CIF) is not list and type(self.CIF) is not tuple:
571            if not os.path.exists(self.CIF):
572                print("Error: template file has disappeared:"+self.CIF)
573                self.CIF = None
574                CIFtxt = "none! (file not found)"
575            else:
576                if len(self.CIF) < 40:
577                    CIFtxt = "File: "+self.CIF
578                else:
579                    CIFtxt = "File: ..."+self.CIF[-40:]
580        else:
581            CIFtxt = "Template is customized"
582        # show template source
583        self.Add(wx.StaticText(panel,wx.ID_ANY,CIFtxt))
584        # show str, button to select file; button to edit (if CIF defined)
585        but = wx.Button(panel,wx.ID_ANY,"Select Template File")
586        but.Bind(wx.EVT_BUTTON,self._onGetTemplateFile)
587        hbox =  wx.BoxSizer(wx.HORIZONTAL)
588        hbox.Add(but,0,0,2)
589        but = wx.Button(panel,wx.ID_ANY,"Edit Template")
590        but.Bind(wx.EVT_BUTTON,self._onEditTemplateContents)
591        hbox.Add(but,0,0,2)
592        #self.Add(hbox,0,wx.ALIGN_CENTER)
593        self.Add(hbox)
594    def _onGetTemplateFile(self,event):
595        dlg = wx.FileDialog(
596            self.cifdefs, message="Read CIF template file",
597            defaultDir=os.getcwd(),
598            defaultFile=self.defaultname,
599            wildcard="CIF (*.cif)|*.cif",
600            style=wx.OPEN | wx.CHANGE_DIR
601            )
602        ret = dlg.ShowModal()
603        fil = dlg.GetPath()
604        dlg.Destroy()
605        if ret == wx.ID_OK:
606            import CifFile as cif # PyCifRW from James Hester
607            try:
608                cf = cif.ReadCif(fil)
609                if len(cf.keys()) == 0: raise Exception,"No CIF data_ blocks found"
610                if len(cf.keys()) != 1:
611                    print('\nWarning: CIF has more than one block, '+fil)
612                self.dict["CIF_template"] = fil
613            except Exception as err:
614                print('\nError reading CIF: '+fil)
615                dlg = wx.MessageDialog(self.cifdefs,
616                                   'Error reading CIF '+fil,
617                                   'Error in CIF file',
618                                   wx.OK)
619                dlg.ShowModal()
620                dlg.Destroy()
621                print(err.message)
622                return
623            self.repaint() #EditCIFDefaults()
624
625    def _onEditTemplateContents(self,event):
626        import CifFile as cif # PyCifRW from James Hester
627        if type(self.CIF) is list or  type(self.CIF) is tuple:
628            dblk,loopstructure = copy.deepcopy(self.CIF) # don't modify original
629        else:
630            dblk,loopstructure = CIF2dict(cif.ReadCif(self.CIF))
631        dlg = EditCIFtemplate(self.cifdefs,dblk,loopstructure,self.defaultname)
632        val = dlg.Post()
633        if val:
634            if dlg.newfile: # results saved in file
635                self.dict["CIF_template"] = dlg.newfile
636            else:
637                self.dict["CIF_template"] = [dlg.cifblk,dlg.loopstructure]
638            self.repaint() #EditCIFDefaults() # note that this does a dlg.Destroy()
639        else:
640            dlg.Destroy()       
641
642#===============================================================================
643# end of misc CIF utilities
644#===============================================================================
645
646class ExportCIF(G2IO.ExportBaseclass):
647    '''Used to create a CIF of an entire project
648    '''
649    def __init__(self,G2frame):
650        super(self.__class__,self).__init__( # fancy way to say <parentclass>.__init__
651            G2frame=G2frame,
652            formatName = 'full CIF',
653            extension='.cif',
654            longFormatName = 'Export project as CIF'
655            )
656        self.author = ''
657
658    def export(self,mode='full'):
659        '''Export a CIF
660
661        :param str mode: "full" (default) to create a complete CIF of project,
662          "simple" for a simple CIF with only coordinates
663        '''
664   
665# ===== define functions for export method =======================================
666        def openCIF(filnam):
667            if DEBUG:
668                self.fp = sys.stdout
669            else:
670                self.fp = open(filnam,'w')
671
672        def closeCIF():
673            if not DEBUG:
674                self.fp.close()
675           
676        def WriteCIFitem(name,value=''):
677            if value:
678                if "\n" in value or len(value)> 70:
679                    if name.strip(): self.fp.write(name+'\n')
680                    self.fp.write('; '+value+'\n')
681                    self.fp.write('; '+'\n')
682                elif " " in value:
683                    if len(name)+len(value) > 65:
684                        self.fp.write(name + '\n   ' + '"' + str(value) + '"'+'\n')
685                    else:
686                        self.fp.write(name + '  ' + '"' + str(value) + '"'+'\n')
687                else:
688                    if len(name)+len(value) > 65:
689                        self.fp.write(name+'\n   ' + value+'\n')
690                    else:
691                        self.fp.write(name+'  ' + value+'\n')
692            else:
693                self.fp.write(name+'\n')
694
695        def WriteAudit():
696            WriteCIFitem('_audit_creation_method',
697                         'created in GSAS-II')
698            WriteCIFitem('_audit_creation_date',self.CIFdate)
699            if self.author:
700                WriteCIFitem('_audit_author_name',self.author)
701            WriteCIFitem('_audit_update_record',
702                         self.CIFdate+'  Initial software-generated CIF')
703
704        def WriteOverall():
705            '''Write out overall refinement information.
706
707            More could be done here, but this is a good start.
708            '''
709            WriteCIFitem('_pd_proc_info_datetime', self.CIFdate)
710            WriteCIFitem('_pd_calc_method', 'Rietveld Refinement')
711            #WriteCIFitem('_refine_ls_shift/su_max',DAT1)
712            #WriteCIFitem('_refine_ls_shift/su_mean',DAT2)
713            #WriteCIFitem('_refine_diff_density_max',rhomax)    #these need to be defined for each phase!
714            #WriteCIFitem('_refine_diff_density_min',rhomin)
715            WriteCIFitem('_computing_structure_refinement','GSAS-II (Toby & Von Dreele, J. Appl. Cryst. 46, 544-549, 2013)')
716            try:
717                vars = str(len(self.OverallParms['Covariance']['varyList']))
718            except:
719                vars = '?'
720            WriteCIFitem('_refine_ls_number_parameters',vars)
721            try:
722                GOF = G2mth.ValEsd(self.OverallParms['Covariance']['Rvals']['GOF'],-0.009)
723            except:
724                GOF = '?'
725            WriteCIFitem('_refine_ls_goodness_of_fit_all',GOF)
726
727            # get restraint info
728            # restraintDict = self.OverallParms.get('Restraints',{})
729            # for i in  self.OverallParms['Constraints']:
730            #     print i
731            #     for j in self.OverallParms['Constraints'][i]:
732            #         print j
733            #WriteCIFitem('_refine_ls_number_restraints',TEXT)
734            # other things to consider reporting
735            # _refine_ls_number_reflns
736            # _refine_ls_goodness_of_fit_obs
737            # _refine_ls_wR_factor_obs
738            # _refine_ls_restrained_S_all
739            # _refine_ls_restrained_S_obs
740
741            # include an overall profile r-factor, if there is more than one powder histogram
742            R = '%.5f'%(self.OverallParms['Covariance']['Rvals']['Rwp']/100.)
743            WriteCIFitem('\n# OVERALL WEIGHTED R-FACTOR')
744            WriteCIFitem('_refine_ls_wR_factor_obs',R)
745                # _refine_ls_R_factor_all
746                # _refine_ls_R_factor_obs               
747            WriteCIFitem('_refine_ls_matrix_type','full')
748            #WriteCIFitem('_refine_ls_matrix_type','userblocks')
749
750        def GetCIF(G2dict,tmplate,defaultname=''):
751            CIFobj = G2dict.get("CIF_template")
752            if defaultname:
753                defaultname = defaultname.encode('ascii','replace').strip().replace(' ','_')
754                defaultname = re.sub(r'[^a-zA-Z0-9_-]','',defaultname)
755                defaultname = tmplate + "_" + defaultname + ".cif"
756            else:
757                defaultname = ''
758            templateDefName = 'template_'+tmplate+'.cif'
759            if not CIFobj: # copying a template
760                for pth in [os.getcwd()]+sys.path:
761                    fil = os.path.join(pth,defaultname)
762                    if os.path.exists(fil) and defaultname: break
763                else:
764                    for pth in sys.path:
765                        fil = os.path.join(pth,templateDefName)
766                        if os.path.exists(fil): break
767                    else:
768                        print(CIFobj+' not found in path!')
769                        return
770                fp = open(fil,'r')
771                txt = fp.read()
772                fp.close()
773            elif type(CIFobj) is not list and type(CIFobj) is not tuple:
774                if not os.path.exists(CIFobj):
775                    print("Error: template file has disappeared:"+CIFobj)
776                    return
777                fp = open(CIFobj,'r')
778                txt = fp.read()
779                fp.close()
780            else:
781                txt = dict2CIF(CIFobj[0],CIFobj[1]).WriteOut()
782            # remove the PyCifRW header, if present
783            #if txt.find('PyCifRW') > -1 and txt.find('data_') > -1:
784            txt = "# GSAS-II edited template follows "+txt[txt.index("data_")+5:]
785            #txt = txt.replace('data_','#')
786            print '***** Template ********'
787            print txt
788            WriteCIFitem(txt)
789
790        def WritePubTemplate():
791            '''insert the publication template ``template_publ.cif`` or a modified
792            version
793            '''
794            GetCIF(self.OverallParms['Controls'],'publ')
795
796        def WritePhaseTemplate(phasenam):
797            '''insert the phase template ``template_phase.cif`` or a modified
798            version for this project
799            '''
800            GetCIF(self.Phases[phasenam]['General'],'phase',phasenam)
801
802        def WritePowderTemplate(hist):
803            '''TODO: insert the phase template ``template_instrument.cif`` or some modified
804            version for this project
805            '''
806            histblk = self.Histograms[hist]["Sample Parameters"]
807            GetCIF(histblk,'powder',histblk['InstrName'])
808
809        def WriteSnglXtalTemplate(hist):
810            '''TODO: insert the single-crystal histogram template
811            for this project
812            '''
813            histblk = self.Histograms[hist]["Instrument Parameters"][0]
814            GetCIF(histblk,'single',histblk['InstrName'])
815
816        def FormatSH(phasenam):
817            'Format a full spherical harmonics texture description as a string'
818            phasedict = self.Phases[phasenam] # pointer to current phase info           
819            pfx = str(phasedict['pId'])+'::'
820            s = ""
821            textureData = phasedict['General']['SH Texture']   
822            if textureData.get('Order'):
823                s += "Spherical Harmonics correction. Order = "+str(textureData['Order'])
824                s += " Model: " + str(textureData['Model']) + "\n    Orientation angles: "
825                for name in ['omega','chi','phi']:
826                    aname = pfx+'SH '+name
827                    s += name + " = "
828                    sig = self.sigDict.get(aname,-0.09)
829                    s += G2mth.ValEsd(self.parmDict[aname],sig)
830                    s += "; "
831                s += "\n"
832                s1 = "    Coefficients:  "
833                for name in textureData['SH Coeff'][1]:
834                    aname = pfx+name
835                    if len(s1) > 60:
836                        s += s1 + "\n"
837                        s1 = "    "
838                    s1 += aname + ' = '
839                    sig = self.sigDict.get(aname,-0.0009)
840                    s1 += G2mth.ValEsd(self.parmDict[aname],sig)
841                    s1 += "; "
842                s += s1
843            return s
844
845        def FormatHAPpo(phasenam):
846            '''return the March-Dollase/SH correction for every
847            histogram in the current phase formatted into a
848            character string
849            '''
850            phasedict = self.Phases[phasenam] # pointer to current phase info           
851            s = ''
852            for histogram in sorted(phasedict['Histograms']):
853                if histogram.startswith("HKLF"): continue # powder only
854                Histogram = self.Histograms.get(histogram)
855                if not Histogram: continue
856                hapData = phasedict['Histograms'][histogram]
857                if hapData['Pref.Ori.'][0] == 'MD':
858                    aname = str(phasedict['pId'])+':'+str(Histogram['hId'])+':MD'
859                    if self.parmDict.get(aname,1.0) != 1.0: continue
860                    sig = self.sigDict.get(aname,-0.009)
861                    if s != "": s += '\n'
862                    s += 'March-Dollase correction'
863                    if len(self.powderDict) > 1:
864                        s += ', histogram '+str(Histogram['hId']+1)
865                    s += ' coef. = ' + G2mth.ValEsd(self.parmDict[aname],sig)
866                    s += ' axis = ' + str(hapData['Pref.Ori.'][3])
867                else: # must be SH
868                    if s != "": s += '\n'
869                    s += 'Simple spherical harmonic correction'
870                    if len(self.powderDict) > 1:
871                        s += ', histogram '+str(Histogram['hId']+1)
872                    s += ' Order = '+str(hapData['Pref.Ori.'][4])+'\n'
873                    s1 = "    Coefficients:  "
874                    for item in hapData['Pref.Ori.'][5]:
875                        aname = str(phasedict['pId'])+':'+str(Histogram['hId'])+':'+item
876                        if len(s1) > 60:
877                            s += s1 + "\n"
878                            s1 = "    "
879                        s1 += aname + ' = '
880                        sig = self.sigDict.get(aname,-0.0009)
881                        s1 += G2mth.ValEsd(self.parmDict[aname],sig)
882                        s1 += "; "
883                    s += s1
884            return s
885        def FormatBackground(bkg,hId):
886            '''Display the Background information as a descriptive text string.
887           
888            TODO: this needs to be expanded to show the diffuse peak and
889            Debye term information as well. (Bob)
890
891            :returns: the text description (str)
892            '''
893            hfx = ':'+str(hId)+':'
894            fxn, bkgdict = bkg
895            terms = fxn[2]
896            txt = 'Background function: "'+fxn[0]+'" function with '+str(terms)+' terms:\n'
897            l = "    "
898            for i,v in enumerate(fxn[3:]):
899                name = '%sBack:%d'%(hfx,i)
900                sig = self.sigDict.get(name,-0.009)
901                if len(l) > 60:
902                    txt += l + '\n'
903                    l = '    '
904                l += G2mth.ValEsd(v,sig)+', '
905            txt += l
906            if bkgdict['nDebye']:
907                txt += '\n  Background Debye function parameters: A, R, U:'
908                names = ['A:','R:','U:']
909                for i in range(bkgdict['nDebye']):
910                    txt += '\n    '
911                    for j in range(3):
912                        name = hfx+'Debye'+names[j]+str(i)
913                        sig = self.sigDict.get(name,-0.009)
914                        txt += G2mth.ValEsd(bkgdict['debyeTerms'][i][2*j],sig)+', '
915            if bkgdict['nPeaks']:
916                txt += '\n  Background peak parameters: pos, int, sig, gam:'
917                names = ['pos:','int:','sig:','gam:']
918                for i in range(bkgdict['nPeaks']):
919                    txt += '\n    '
920                    for j in range(4):
921                        name = hfx+'BkPk'+names[j]+str(i)
922                        sig = self.sigDict.get(name,-0.009)
923                        txt += G2mth.ValEsd(bkgdict['peaksList'][i][2*j],sig)+', '
924            return txt
925
926        def FormatInstProfile(instparmdict,hId):
927            '''Format the instrumental profile parameters with a
928            string description. Will only be called on PWDR histograms
929            '''
930            s = ''
931            inst = instparmdict[0]
932            hfx = ':'+str(hId)+':'
933            if 'C' in inst['Type'][0]:
934                s = 'Finger-Cox-Jephcoat function parameters U, V, W, X, Y, SH/L:\n'
935                s += '  peak variance(Gauss) = Utan(Th)^2^+Vtan(Th)+W:\n'
936                s += '  peak HW(Lorentz) = X/cos(Th)+Ytan(Th); SH/L = S/L+H/L\n'
937                s += '  U, V, W in (centideg)^2^, X & Y in centideg\n    '
938                for item in ['U','V','W','X','Y','SH/L']:
939                    name = hfx+item
940                    sig = self.sigDict.get(name,-0.009)
941                    s += G2mth.ValEsd(inst[item][1],sig)+', '                   
942            elif 'T' in inst['Type'][0]:    #to be tested after TOF Rietveld done
943                s = 'Von Dreele-Jorgenson-Windsor function parameters\n'+ \
944                    '   alpha, beta-0, beta-1, beta-q, sig-0, sig-1, sig-q, X, Y:\n    '
945                for item in ['alpha','bet-0','bet-1','bet-q','sig-0','sig-1','sig-q','X','Y']:
946                    name = hfx+item
947                    sig = self.sigDict.get(name,-0.009)
948                    s += G2mth.ValEsd(inst[item][1],sig)+', '
949            return s
950
951        def FormatPhaseProfile(phasenam):
952            '''Format the phase-related profile parameters (size/strain)
953            with a string description.
954            return an empty string or None if there are no
955            powder histograms for this phase.
956            '''
957            s = ''
958            phasedict = self.Phases[phasenam] # pointer to current phase info
959            SGData = phasedict['General'] ['SGData']         
960            for histogram in sorted(phasedict['Histograms']):
961                if histogram.startswith("HKLF"): continue # powder only
962                Histogram = self.Histograms.get(histogram)
963                if not Histogram: continue
964                hapData = phasedict['Histograms'][histogram]
965                pId = phasedict['pId']
966                hId = Histogram['hId']
967                phfx = '%d:%d:'%(pId,hId)
968                size = hapData['Size']
969                mustrain = hapData['Mustrain']
970                hstrain = hapData['HStrain']
971                s = '  Crystallite size model "%s" for %s (microns)\n  '%(size[0],phasenam)
972                names = ['Size;i','Size;mx']
973                if 'uniax' in size[0]:
974                    names = ['Size;i','Size;a','Size;mx']
975                    s += 'anisotropic axis is %s\n  '%(str(size[3]))
976                    s += 'parameters: equatorial size, axial size, G/L mix\n    '
977                    for i,item in enumerate(names):
978                        name = phfx+item
979                        sig = self.sigDict.get(name,-0.009)
980                        s += G2mth.ValEsd(size[1][i],sig)+', '
981                elif 'ellip' in size[0]:
982                    s += 'parameters: S11, S22, S33, S12, S13, S23, G/L mix\n    '
983                    for i in range(6):
984                        name = phfx+'Size:'+str(i)
985                        sig = self.sigDict.get(name,-0.009)
986                        s += G2mth.ValEsd(size[4][i],sig)+', '
987                    sig = self.sigDict.get(phfx+'Size;mx',-0.009)
988                    s += G2mth.ValEsd(size[1][2],sig)+', '                                           
989                else:       #isotropic
990                    s += 'parameters: Size, G/L mix\n    '
991                    i = 0
992                    for item in names:
993                        name = phfx+item
994                        sig = self.sigDict.get(name,-0.009)
995                        s += G2mth.ValEsd(size[1][i],sig)+', '
996                        i = 2    #skip the aniso value               
997                s += '\n  Mustrain model "%s" for %s (10^6^)\n  '%(mustrain[0],phasenam)
998                names = ['Mustrain;i','Mustrain;mx']
999                if 'uniax' in mustrain[0]:
1000                    names = ['Mustrain;i','Mustrain;a','Mustrain;mx']
1001                    s += 'anisotropic axis is %s\n  '%(str(size[3]))
1002                    s += 'parameters: equatorial mustrain, axial mustrain, G/L mix\n    '
1003                    for i,item in enumerate(names):
1004                        name = phfx+item
1005                        sig = self.sigDict.get(name,-0.009)
1006                        s += G2mth.ValEsd(mustrain[1][i],sig)+', '
1007                elif 'general' in mustrain[0]:
1008                    names = 'parameters: '
1009                    for i,name in enumerate(G2spc.MustrainNames(SGData)):
1010                        names += name+', '
1011                        if i == 9:
1012                            names += '\n  '
1013                    names += 'G/L mix\n    '
1014                    s += names
1015                    txt = ''
1016                    for i in range(len(mustrain[4])):
1017                        name = phfx+'Mustrain:'+str(i)
1018                        sig = self.sigDict.get(name,-0.009)
1019                        if len(txt) > 60:
1020                            s += txt+'\n    '
1021                            txt = ''
1022                        txt += G2mth.ValEsd(mustrain[4][i],sig)+', '
1023                    s += txt                                           
1024                    sig = self.sigDict.get(phfx+'Mustrain;mx',-0.009)
1025                    s += G2mth.ValEsd(mustrain[1][2],sig)+', '
1026                   
1027                else:       #isotropic
1028                    s += '  parameters: Mustrain, G/L mix\n    '
1029                    i = 0
1030                    for item in names:
1031                        name = phfx+item
1032                        sig = self.sigDict.get(name,-0.009)
1033                        s += G2mth.ValEsd(mustrain[1][i],sig)+', '
1034                        i = 2    #skip the aniso value               
1035                s += '\n  Macrostrain for %s\n'%(phasenam)
1036                txt = '  parameters: '
1037                names = G2spc.HStrainNames(SGData)
1038                for name in names:
1039                    txt += name+', '
1040                s += txt+'\n    '
1041                for i in range(len(names)):
1042                    name = phfx+name[i]
1043                    sig = self.sigDict.get(name,-0.009)
1044                    s += G2mth.ValEsd(hstrain[0][i],sig)+', '
1045            return s
1046       
1047        def FmtAtomType(sym):
1048            'Reformat a GSAS-II atom type symbol to match CIF rules'
1049            sym = sym.replace('_','') # underscores are not allowed: no isotope designation?
1050            # in CIF, oxidation state sign symbols come after, not before
1051            if '+' in sym:
1052                sym = sym.replace('+','') + '+'
1053            elif '-' in sym:
1054                sym = sym.replace('-','') + '-'
1055            return sym
1056           
1057        def PutInCol(val,wid):
1058            '''Pad a value to >=wid+1 columns by adding spaces at the end. Always
1059            adds at least one space
1060            '''
1061            val = str(val).replace(' ','')
1062            if not val: val = '?'
1063            fmt = '{:' + str(wid) + '} '
1064            return fmt.format(val)
1065
1066        def MakeUniqueLabel(lbl,labellist):
1067            'Make sure that every atom label is unique'
1068            lbl = lbl.strip()
1069            if not lbl: # deal with a blank label
1070                lbl = 'A_1'
1071            if lbl not in labellist:
1072                labellist.append(lbl)
1073                return lbl
1074            i = 1
1075            prefix = lbl
1076            if '_' in lbl:
1077                prefix = lbl[:lbl.rfind('_')]
1078                suffix = lbl[lbl.rfind('_')+1:]
1079                try:
1080                    i = int(suffix)+1
1081                except:
1082                    pass
1083            while prefix+'_'+str(i) in labellist:
1084                i += 1
1085            else:
1086                lbl = prefix+'_'+str(i)
1087                labellist.append(lbl)
1088
1089        def WriteAtomsNuclear(phasenam):
1090            'Write atom positions to CIF'
1091            phasedict = self.Phases[phasenam] # pointer to current phase info
1092            General = phasedict['General']
1093            cx,ct,cs,cia = General['AtomPtrs']
1094            Atoms = phasedict['Atoms']
1095            cfrac = cx+3
1096            fpfx = str(phasedict['pId'])+'::Afrac:'       
1097            for i,at in enumerate(Atoms):
1098                fval = self.parmDict.get(fpfx+str(i),at[cfrac])
1099                if fval != 0.0:
1100                    break
1101            else:
1102                WriteCIFitem('\n# PHASE HAS NO ATOMS!')
1103                return
1104               
1105            WriteCIFitem('\n# ATOMIC COORDINATES AND DISPLACEMENT PARAMETERS')
1106            WriteCIFitem('loop_ '+
1107                         '\n\t_atom_site_label'+
1108                         '\n\t_atom_site_type_symbol'+
1109                         '\n\t_atom_site_fract_x'+
1110                         '\n\t_atom_site_fract_y'+
1111                         '\n\t_atom_site_fract_z'+
1112                         '\n\t_atom_site_occupancy'+
1113                         '\n\t_atom_site_adp_type'+
1114                         '\n\t_atom_site_U_iso_or_equiv'+
1115                         '\n\t_atom_site_symmetry_multiplicity')
1116
1117            varnames = {cx:'Ax',cx+1:'Ay',cx+2:'Az',cx+3:'Afrac',
1118                        cia+1:'AUiso',cia+2:'AU11',cia+3:'AU22',cia+4:'AU33',
1119                        cia+5:'AU12',cia+6:'AU13',cia+7:'AU23'}
1120            self.labellist = []
1121           
1122            pfx = str(phasedict['pId'])+'::'
1123            # loop over all atoms
1124            naniso = 0
1125            for i,at in enumerate(Atoms):
1126                s = PutInCol(MakeUniqueLabel(at[ct-1],self.labellist),6) # label
1127                fval = self.parmDict.get(fpfx+str(i),at[cfrac])
1128                if fval == 0.0: continue # ignore any atoms that have a occupancy set to 0 (exact)
1129                s += PutInCol(FmtAtomType(at[ct]),4) # type
1130                if at[cia] == 'I':
1131                    adp = 'Uiso '
1132                else:
1133                    adp = 'Uani '
1134                    naniso += 1
1135                    # compute Uequiv crudely
1136                    # correct: Defined as "1/3 trace of diagonalized U matrix".
1137                    # SEE cell2GS & Uij2Ueqv to GSASIIlattice. Former is needed to make the GS matrix used by the latter.
1138                    t = 0.0
1139                    for j in (2,3,4):
1140                        var = pfx+varnames[cia+j]+":"+str(i)
1141                        t += self.parmDict.get(var,at[cia+j])
1142                for j in (cx,cx+1,cx+2,cx+3,cia,cia+1):
1143                    if j in (cx,cx+1,cx+2):
1144                        dig = 11
1145                        sigdig = -0.00009
1146                    else:
1147                        dig = 10
1148                        sigdig = -0.009
1149                    if j == cia:
1150                        s += adp
1151                    else:
1152                        var = pfx+varnames[j]+":"+str(i)
1153                        dvar = pfx+"d"+varnames[j]+":"+str(i)
1154                        if dvar not in self.sigDict:
1155                            dvar = var
1156                        if j == cia+1 and adp == 'Uani ':
1157                            val = t/3.
1158                            sig = sigdig
1159                        else:
1160                            #print var,(var in self.parmDict),(var in self.sigDict)
1161                            val = self.parmDict.get(var,at[j])
1162                            sig = self.sigDict.get(dvar,sigdig)
1163                        s += PutInCol(G2mth.ValEsd(val,sig),dig)
1164                s += PutInCol(at[cs+1],3)
1165                WriteCIFitem(s)
1166            if naniso == 0: return
1167            # now loop over aniso atoms
1168            WriteCIFitem('\nloop_' + '\n\t_atom_site_aniso_label' + 
1169                         '\n\t_atom_site_aniso_U_11' + '\n\t_atom_site_aniso_U_12' +
1170                         '\n\t_atom_site_aniso_U_13' + '\n\t_atom_site_aniso_U_22' +
1171                         '\n\t_atom_site_aniso_U_23' + '\n\t_atom_site_aniso_U_33')
1172            for i,at in enumerate(Atoms):
1173                fval = self.parmDict.get(fpfx+str(i),at[cfrac])
1174                if fval == 0.0: continue # ignore any atoms that have a occupancy set to 0 (exact)
1175                if at[cia] == 'I': continue
1176                s = PutInCol(self.labellist[i],6) # label
1177                for j in (2,3,4,5,6,7):
1178                    sigdig = -0.0009
1179                    var = pfx+varnames[cia+j]+":"+str(i)
1180                    val = self.parmDict.get(var,at[cia+j])
1181                    sig = self.sigDict.get(var,sigdig)
1182                    s += PutInCol(G2mth.ValEsd(val,sig),11)
1183                WriteCIFitem(s)
1184
1185        def HillSortElements(elmlist):
1186            '''Sort elements in "Hill" order: C, H, others, (where others
1187            are alphabetical).
1188
1189            :params list elmlist: a list of element strings
1190
1191            :returns: a sorted list of element strings
1192            '''
1193            newlist = []
1194            oldlist = elmlist[:]
1195            for elm in ('C','H'):
1196                if elm in elmlist:
1197                    newlist.append(elm)
1198                    oldlist.pop(oldlist.index(elm))
1199            return newlist+sorted(oldlist)
1200
1201        def WriteComposition(phasenam):
1202            '''determine the composition for the unit cell, crudely determine Z and
1203            then compute the composition in formula units
1204            '''
1205            phasedict = self.Phases[phasenam] # pointer to current phase info
1206            General = phasedict['General']
1207            Z = General.get('cellZ',0.0)
1208            cx,ct,cs,cia = General['AtomPtrs']
1209            Atoms = phasedict['Atoms']
1210            fpfx = str(phasedict['pId'])+'::Afrac:'       
1211            cfrac = cx+3
1212            cmult = cs+1
1213            compDict = {} # combines H,D & T
1214            sitemultlist = []
1215            massDict = dict(zip(General['AtomTypes'],General['AtomMass']))
1216            cellmass = 0
1217            for i,at in enumerate(Atoms):
1218                atype = at[ct].strip()
1219                if atype.find('-') != -1: atype = atype.split('-')[0]
1220                if atype.find('+') != -1: atype = atype.split('+')[0]
1221                atype = atype[0].upper()+atype[1:2].lower() # force case conversion
1222                if atype == "D" or atype == "D": atype = "H"
1223                fvar = fpfx+str(i)
1224                fval = self.parmDict.get(fvar,at[cfrac])
1225                mult = at[cmult]
1226                if not massDict.get(at[ct]):
1227                    print('Error: No mass found for atom type '+at[ct])
1228                    print('Will not compute cell contents for phase '+phasenam)
1229                    return
1230                cellmass += massDict[at[ct]]*mult*fval
1231                compDict[atype] = compDict.get(atype,0.0) + mult*fval
1232                if fval == 1: sitemultlist.append(mult)
1233            if len(compDict.keys()) == 0: return # no elements!
1234            if Z < 1: # Z has not been computed or set by user
1235                Z = 1
1236                for i in range(2,min(sitemultlist)+1):
1237                    for m in sitemultlist:
1238                        if m % i != 0:
1239                            break
1240                        else:
1241                            Z = i
1242                General['cellZ'] = Z # save it
1243
1244            # when scattering factors are included in the CIF, this needs to be
1245            # added to the loop here but only in the one-block case.
1246            # For multiblock CIFs, scattering factors go in the histogram
1247            # blocks  (for all atoms in all appropriate phases) - an example?:
1248#loop_
1249#    _atom_type_symbol
1250#    _atom_type_description
1251#    _atom_type_scat_dispersion_real
1252#    _atom_type_scat_dispersion_imag
1253#    _atom_type_scat_source
1254#    'C' 'C' 0.0033 0.0016
1255#                         'International Tables Vol C Tables 4.2.6.8 and 6.1.1.4'
1256#    'H' 'H' 0.0000 0.0000
1257#                         'International Tables Vol C Tables 4.2.6.8 and 6.1.1.4'
1258#    'P' 'P' 0.1023 0.0942
1259#                         'International Tables Vol C Tables 4.2.6.8 and 6.1.1.4'
1260#    'Cl' 'Cl' 0.1484 0.1585
1261#                         'International Tables Vol C Tables 4.2.6.8 and 6.1.1.4'
1262#    'Cu' 'Cu' 0.3201 1.2651
1263#                         'International Tables Vol C Tables 4.2.6.8 and 6.1.1.4'
1264
1265            #if oneblock: # add scattering factors for current phase here
1266            WriteCIFitem('\nloop_  _atom_type_symbol _atom_type_number_in_cell')
1267            formula = ''
1268            reload(G2mth)
1269            for elem in HillSortElements(compDict.keys()):
1270                WriteCIFitem('  ' + PutInCol(elem,4) +
1271                             G2mth.ValEsd(compDict[elem],-0.009,True))
1272                if formula: formula += " "
1273                formula += elem
1274                if compDict[elem] == Z: continue
1275                formula += G2mth.ValEsd(compDict[elem]/Z,-0.009,True)
1276            WriteCIFitem( '\n# Note that Z affects _cell_formula_sum and _weight')
1277            WriteCIFitem( '_cell_formula_units_Z',str(Z))
1278            WriteCIFitem( '_chemical_formula_sum',formula)
1279            WriteCIFitem( '_chemical_formula_weight',
1280                          G2mth.ValEsd(cellmass/Z,-0.09,True))
1281
1282        def WriteDistances(phasenam,SymOpList,offsetList,symOpList,G2oprList):
1283            '''Report bond distances and angles for the CIF
1284
1285            Note that _geom_*_symmetry_* fields are values of form
1286            n_klm where n is the symmetry operation in SymOpList (counted
1287            starting with 1) and (k-5, l-5, m-5) are translations to add
1288            to (x,y,z). See
1289            http://www.iucr.org/__data/iucr/cifdic_html/1/cif_core.dic/Igeom_angle_site_symmetry_.html
1290
1291            TODO: need a method to select publication flags for distances/angles
1292            '''
1293            phasedict = self.Phases[phasenam] # pointer to current phase info           
1294            Atoms = phasedict['Atoms']
1295            generalData = phasedict['General']
1296            cx,ct,cs,cia = phasedict['General']['AtomPtrs']
1297            cn = ct-1
1298            fpfx = str(phasedict['pId'])+'::Afrac:'       
1299            cfrac = cx+3
1300            DisAglData = {}
1301            DisAglCtls = {}
1302            # create a list of atoms, but skip atoms with zero occupancy
1303            xyz = []
1304            fpfx = str(phasedict['pId'])+'::Afrac:'       
1305            for i,atom in enumerate(Atoms):
1306                if self.parmDict.get(fpfx+str(i),atom[cfrac]) == 0.0: continue
1307                xyz.append([i,]+atom[cn:cn+2]+atom[cx:cx+3])
1308            if 'DisAglCtls' in generalData:
1309                DisAglCtls = generalData['DisAglCtls']
1310            else:
1311                dlg = G2gd.DisAglDialog(self.G2frame,DisAglCtls,generalData)
1312                if dlg.ShowModal() == wx.ID_OK:
1313                    DisAglCtls = dlg.GetData()
1314                    generalData['DisAglCtls'] = DisAglCtls
1315                else:
1316                    dlg.Destroy()
1317                    return
1318                dlg.Destroy()
1319            DisAglData['OrigAtoms'] = xyz
1320            DisAglData['TargAtoms'] = xyz
1321            SymOpList,offsetList,symOpList,G2oprList = G2spc.AllOps(
1322                generalData['SGData'])
1323
1324            xpandSGdata = generalData['SGData'].copy()
1325            xpandSGdata.update({'SGOps':symOpList,
1326                                'SGInv':False,
1327                                'SGLatt':'P',
1328                                'SGCen':np.array([[0, 0, 0]]),})
1329            DisAglData['SGData'] = xpandSGdata
1330
1331            DisAglData['Cell'] = generalData['Cell'][1:] #+ volume
1332            if 'pId' in phasedict:
1333                DisAglData['pId'] = phasedict['pId']
1334                DisAglData['covData'] = self.OverallParms['Covariance']
1335            try:
1336                AtomLabels,DistArray,AngArray = G2stMn.RetDistAngle(DisAglCtls,DisAglData)
1337            except KeyError:        # inside DistAngle for missing atom types in DisAglCtls
1338                print('**** ERROR - try again but do "Reset" to fill in missing atom types ****')
1339                   
1340            # loop over interatomic distances for this phase
1341            WriteCIFitem('\n# MOLECULAR GEOMETRY')
1342            WriteCIFitem('loop_' + 
1343                         '\n\t_geom_bond_atom_site_label_1' +
1344                         '\n\t_geom_bond_atom_site_label_2' + 
1345                         '\n\t_geom_bond_distance' + 
1346                         '\n\t_geom_bond_site_symmetry_1' + 
1347                         '\n\t_geom_bond_site_symmetry_2' + 
1348                         '\n\t_geom_bond_publ_flag')
1349
1350            for i in sorted(AtomLabels.keys()):
1351                Dist = DistArray[i]
1352                for D in Dist:
1353                    line = '  '+PutInCol(AtomLabels[i],6)+PutInCol(AtomLabels[D[0]],6)
1354                    sig = D[4]
1355                    if sig == 0: sig = -0.00009
1356                    line += PutInCol(G2mth.ValEsd(D[3],sig,True),10)
1357                    line += "  1_555 "
1358                    line += " {:3d}_".format(D[2])
1359                    for d in D[1]:
1360                        line += "{:1d}".format(d+5)
1361                    line += " yes"
1362                    WriteCIFitem(line)
1363
1364            # loop over interatomic angles for this phase
1365            WriteCIFitem('\nloop_' + 
1366                         '\n\t_geom_angle_atom_site_label_1' + 
1367                         '\n\t_geom_angle_atom_site_label_2' + 
1368                         '\n\t_geom_angle_atom_site_label_3' + 
1369                         '\n\t_geom_angle' + 
1370                         '\n\t_geom_angle_site_symmetry_1' +
1371                         '\n\t_geom_angle_site_symmetry_2' + 
1372                         '\n\t_geom_angle_site_symmetry_3' + 
1373                         '\n\t_geom_angle_publ_flag')
1374
1375            for i in sorted(AtomLabels.keys()):
1376                Dist = DistArray[i]
1377                for k,j,tup in AngArray[i]:
1378                    Dj = Dist[j]
1379                    Dk = Dist[k]
1380                    line = '  '+PutInCol(AtomLabels[Dj[0]],6)+PutInCol(AtomLabels[i],6)+PutInCol(AtomLabels[Dk[0]],6)
1381                    sig = tup[1]
1382                    if sig == 0: sig = -0.009
1383                    line += PutInCol(G2mth.ValEsd(tup[0],sig,True),10)
1384                    line += " {:3d}_".format(Dj[2])
1385                    for d in Dj[1]:
1386                        line += "{:1d}".format(d+5)
1387                    line += "  1_555 "
1388                    line += " {:3d}_".format(Dk[2])
1389                    for d in Dk[1]:
1390                        line += "{:1d}".format(d+5)
1391                    line += " yes"
1392                    WriteCIFitem(line)
1393
1394        def WritePhaseInfo(phasenam):
1395            WriteCIFitem('\n# phase info for '+str(phasenam) + ' follows')
1396            phasedict = self.Phases[phasenam] # pointer to current phase info           
1397            WriteCIFitem('_pd_phase_name', phasenam)
1398            pfx = str(phasedict['pId'])+'::'
1399            A,sigA = G2stIO.cellFill(pfx,phasedict['General']['SGData'],self.parmDict,self.sigDict)
1400            cellSig = G2stIO.getCellEsd(pfx,
1401                                       phasedict['General']['SGData'],A,
1402                                       self.OverallParms['Covariance'])  # returns 7 vals, includes sigVol
1403            cellList = G2lat.A2cell(A) + (G2lat.calc_V(A),)
1404            defsigL = 3*[-0.00001] + 3*[-0.001] + [-0.01] # significance to use when no sigma
1405            names = ['length_a','length_b','length_c',
1406                     'angle_alpha','angle_beta ','angle_gamma',
1407                     'volume']
1408            prevsig = 0
1409            for lbl,defsig,val,sig in zip(names,defsigL,cellList,cellSig):
1410                if sig:
1411                    txt = G2mth.ValEsd(val,sig)
1412                    prevsig = -sig # use this as the significance for next value
1413                else:
1414                    txt = G2mth.ValEsd(val,min(defsig,prevsig),True)
1415                WriteCIFitem('_cell_'+lbl,txt)
1416                   
1417            WriteCIFitem('_symmetry_cell_setting',
1418                         phasedict['General']['SGData']['SGSys'])
1419
1420            spacegroup = phasedict['General']['SGData']['SpGrp'].strip()
1421            # regularize capitalization and remove trailing H/R
1422            spacegroup = spacegroup[0].upper() + spacegroup[1:].lower().rstrip('rh ')
1423            WriteCIFitem('_symmetry_space_group_name_H-M',spacegroup)
1424
1425            # generate symmetry operations including centering and center of symmetry
1426            SymOpList,offsetList,symOpList,G2oprList = G2spc.AllOps(
1427                phasedict['General']['SGData'])
1428            WriteCIFitem('loop_\n    _space_group_symop_id\n    _space_group_symop_operation_xyz')
1429            for i,op in enumerate(SymOpList,start=1):
1430                WriteCIFitem('   {:3d}  {:}'.format(i,op.lower()))
1431
1432            # loop over histogram(s) used in this phase
1433            if not oneblock and not self.quickmode:
1434                # report pointers to the histograms used in this phase
1435                histlist = []
1436                for hist in self.Phases[phasenam]['Histograms']:
1437                    if self.Phases[phasenam]['Histograms'][hist]['Use']:
1438                        if phasebyhistDict.get(hist):
1439                            phasebyhistDict[hist].append(phasenam)
1440                        else:
1441                            phasebyhistDict[hist] = [phasenam,]
1442                        blockid = datablockidDict.get(hist)
1443                        if not blockid:
1444                            print("Internal error: no block for data. Phase "+str(
1445                                phasenam)+" histogram "+str(hist))
1446                            histlist = []
1447                            break
1448                        histlist.append(blockid)
1449
1450                if len(histlist) == 0:
1451                    WriteCIFitem('# Note: phase has no associated data')
1452
1453            # report atom params
1454            if phasedict['General']['Type'] == 'nuclear':        #this needs macromolecular variant, etc!
1455                WriteAtomsNuclear(phasenam)
1456            else:
1457                raise Exception,"no export for mm coordinates implemented"
1458            # report cell contents
1459            WriteComposition(phasenam)
1460            if not self.quickmode:      # report distances and angles
1461                WriteDistances(phasenam,SymOpList,offsetList,symOpList,G2oprList)
1462               
1463        def Yfmt(ndec,val):
1464            'Format intensity values'
1465            out = ("{:."+str(ndec)+"f}").format(val)
1466            out = out.rstrip('0')  # strip zeros to right of decimal
1467            return out.rstrip('.')  # and decimal place when not needed
1468           
1469        def WriteReflStat(refcount,hklmin,hklmax,dmin,dmax,nRefSets=1):
1470            WriteCIFitem('_reflns_number_total', str(refcount))
1471            if hklmin is not None and nRefSets == 1: # hkl range has no meaning with multiple phases
1472                WriteCIFitem('_reflns_limit_h_min', str(int(hklmin[0])))
1473                WriteCIFitem('_reflns_limit_h_max', str(int(hklmax[0])))
1474                WriteCIFitem('_reflns_limit_k_min', str(int(hklmin[1])))
1475                WriteCIFitem('_reflns_limit_k_max', str(int(hklmax[1])))
1476                WriteCIFitem('_reflns_limit_l_min', str(int(hklmin[2])))
1477                WriteCIFitem('_reflns_limit_l_max', str(int(hklmax[2])))
1478            if hklmin is not None:
1479                WriteCIFitem('_reflns_d_resolution_low  ', G2mth.ValEsd(dmax,-0.009))
1480                WriteCIFitem('_reflns_d_resolution_high ', G2mth.ValEsd(dmin,-0.009))
1481
1482        def WritePowderData(histlbl):
1483            histblk = self.Histograms[histlbl]
1484            inst = histblk['Instrument Parameters'][0]
1485            hId = histblk['hId']
1486            pfx = ':' + str(hId) + ':'
1487           
1488            if 'Lam1' in inst:
1489                ratio = self.parmDict.get('I(L2)/I(L1)',inst['I(L2)/I(L1)'][1])
1490                sratio = self.sigDict.get('I(L2)/I(L1)',-0.0009)
1491                lam1 = self.parmDict.get('Lam1',inst['Lam1'][1])
1492                slam1 = self.sigDict.get('Lam1',-0.00009)
1493                lam2 = self.parmDict.get('Lam2',inst['Lam2'][1])
1494                slam2 = self.sigDict.get('Lam2',-0.00009)
1495                # always assume Ka1 & Ka2 if two wavelengths are present
1496                WriteCIFitem('_diffrn_radiation_type','K\\a~1,2~')
1497                WriteCIFitem('loop_' + 
1498                             '\n\t_diffrn_radiation_wavelength' +
1499                             '\n\t_diffrn_radiation_wavelength_wt' + 
1500                             '\n\t_diffrn_radiation_wavelength_id')
1501                WriteCIFitem('  ' + PutInCol(G2mth.ValEsd(lam1,slam1),15)+
1502                             PutInCol('1.0',15) + 
1503                             PutInCol('1',5))
1504                WriteCIFitem('  ' + PutInCol(G2mth.ValEsd(lam2,slam2),15)+
1505                             PutInCol(G2mth.ValEsd(ratio,sratio),15)+
1506                             PutInCol('2',5))               
1507            else:
1508                lam1 = self.parmDict.get('Lam',inst['Lam'][1])
1509                slam1 = self.sigDict.get('Lam',-0.00009)
1510                WriteCIFitem('_diffrn_radiation_wavelength',G2mth.ValEsd(lam1,slam1))
1511
1512            if not oneblock:
1513                if not phasebyhistDict.get(histlbl):
1514                    WriteCIFitem('\n# No phases associated with this data set')
1515                else:
1516                    WriteCIFitem('\n# PHASE TABLE')
1517                    WriteCIFitem('loop_' +
1518                                 '\n\t_pd_phase_id' + 
1519                                 '\n\t_pd_phase_block_id' + 
1520                                 '\n\t_pd_phase_mass_%')
1521                    wtFrSum = 0.
1522                    for phasenam in phasebyhistDict.get(histlbl):
1523                        hapData = self.Phases[phasenam]['Histograms'][histlbl]
1524                        General = self.Phases[phasenam]['General']
1525                        wtFrSum += hapData['Scale'][0]*General['Mass']
1526
1527                    for phasenam in phasebyhistDict.get(histlbl):
1528                        hapData = self.Phases[phasenam]['Histograms'][histlbl]
1529                        General = self.Phases[phasenam]['General']
1530                        wtFr = hapData['Scale'][0]*General['Mass']/wtFrSum
1531                        pfx = str(self.Phases[phasenam]['pId'])+':'+str(hId)+':'
1532                        if pfx+'Scale' in self.sigDict:
1533                            sig = self.sigDict[pfx+'Scale']*wtFr/hapData['Scale'][0]
1534                        else:
1535                            sig = -0.0001
1536                        WriteCIFitem(
1537                            '  '+
1538                            str(self.Phases[phasenam]['pId']) +
1539                            '  '+datablockidDict[phasenam]+
1540                            '  '+G2mth.ValEsd(wtFr,sig)
1541                            )
1542                    WriteCIFitem('loop_' +
1543                                 '\n\t_gsas_proc_phase_R_F_factor' +
1544                                 '\n\t_gsas_proc_phase_R_Fsqd_factor' +
1545                                 '\n\t_gsas_proc_phase_id' +
1546                                 '\n\t_gsas_proc_phase_block_id')
1547                    for phasenam in phasebyhistDict.get(histlbl):
1548                        pfx = str(self.Phases[phasenam]['pId'])+':'+str(hId)+':'
1549                        WriteCIFitem(
1550                            '  '+
1551                            '  '+G2mth.ValEsd(histblk[pfx+'Rf']/100.,-.00009) +
1552                            '  '+G2mth.ValEsd(histblk[pfx+'Rf^2']/100.,-.00009)+
1553                            '  '+str(self.Phases[phasenam]['pId'])+
1554                            '  '+datablockidDict[phasenam]
1555                            )
1556            else:
1557                # single phase in this histogram
1558                pfx = '0:'+str(hId)+':'
1559                WriteCIFitem('_refine_ls_R_F_factor      ','%.5f'%(histblk[pfx+'Rf']/100.))
1560                WriteCIFitem('_refine_ls_R_Fsqd_factor   ','%.5f'%(histblk[pfx+'Rf^2']/100.))
1561               
1562            WriteCIFitem('_pd_proc_ls_prof_R_factor   ','%.5f'%(histblk['R']/100.))
1563            WriteCIFitem('_pd_proc_ls_prof_wR_factor  ','%.5f'%(histblk['wR']/100.))
1564            WriteCIFitem('_gsas_proc_ls_prof_R_B_factor ','%.5f'%(histblk['Rb']/100.))
1565            WriteCIFitem('_gsas_proc_ls_prof_wR_B_factor','%.5f'%(histblk['wRb']/100.))
1566            WriteCIFitem('_pd_proc_ls_prof_wR_expected','%.5f'%(histblk['wRmin']/100.))
1567
1568            if histblk['Instrument Parameters'][0]['Type'][1][1] == 'X':
1569                WriteCIFitem('_diffrn_radiation_probe','x-ray')
1570                pola = histblk['Instrument Parameters'][0].get('Polariz.')
1571                if pola:
1572                    pfx = ':' + str(hId) + ':'
1573                    sig = self.sigDict.get(pfx+'Polariz.',-0.0009)
1574                    txt = G2mth.ValEsd(pola[1],sig)
1575                    WriteCIFitem('_diffrn_radiation_polarisn_ratio',txt)
1576            elif histblk['Instrument Parameters'][0]['Type'][1][1] == 'N':
1577                WriteCIFitem('_diffrn_radiation_probe','neutron')
1578            # TOF (note that this may not be defined)
1579            #if histblk['Instrument Parameters'][0]['Type'][1][2] == 'T':
1580            #    WriteCIFitem('_pd_meas_2theta_fixed',text)
1581           
1582
1583            # TODO: this will need help from Bob
1584            #if not oneblock:
1585            #WriteCIFitem('\n# SCATTERING FACTOR INFO')
1586            #WriteCIFitem('loop_  _atom_type_symbol')
1587            #if histblk['Instrument Parameters'][0]['Type'][1][1] == 'X':
1588            #    WriteCIFitem('      _atom_type_scat_dispersion_real')
1589            #    WriteCIFitem('      _atom_type_scat_dispersion_imag')
1590            #    for lbl in ('a1','a2','a3', 'a4', 'b1', 'b2', 'b3', 'b4', 'c'):
1591            #        WriteCIFitem('      _atom_type_scat_Cromer_Mann_'+lbl)
1592            #elif histblk['Instrument Parameters'][0]['Type'][1][1] == 'N':
1593            #    WriteCIFitem('      _atom_type_scat_length_neutron')
1594            #WriteCIFitem('      _atom_type_scat_source')
1595
1596            WriteCIFitem('_pd_proc_ls_background_function',FormatBackground(histblk['Background'],histblk['hId']))
1597
1598            # TODO: this will need help from Bob
1599            #WriteCIFitem('_exptl_absorpt_process_details','?')
1600            #WriteCIFitem('_exptl_absorpt_correction_T_min','?')
1601            #WriteCIFitem('_exptl_absorpt_correction_T_max','?')
1602            #C extinction
1603            #WRITE(IUCIF,'(A)') '# Extinction correction'
1604            #CALL WRVAL(IUCIF,'_gsas_exptl_extinct_corr_T_min',TEXT(1:10))
1605            #CALL WRVAL(IUCIF,'_gsas_exptl_extinct_corr_T_max',TEXT(11:20))
1606
1607            if not oneblock:                 # instrumental profile terms go here
1608                WriteCIFitem('_pd_proc_ls_profile_function', 
1609                    FormatInstProfile(histblk["Instrument Parameters"],histblk['hId']))
1610
1611            #refprx = '_refln.' # mm
1612            refprx = '_refln_' # normal
1613            WriteCIFitem('\n# STRUCTURE FACTOR TABLE')           
1614            # compute maximum intensity reflection
1615            Imax = 0
1616            for phasenam in histblk['Reflection Lists']:
1617                scale = self.Phases[phasenam]['Histograms'][histlbl]['Scale'][0]
1618                Icorr = np.array([refl[13] for refl in histblk['Reflection Lists'][phasenam]])[0]
1619                FO2 = np.array([refl[8] for refl in histblk['Reflection Lists'][phasenam]])
1620                I100 = scale*FO2*Icorr
1621                Imax = max(Imax,max(I100))
1622
1623            WriteCIFitem('loop_')
1624            if len(histblk['Reflection Lists'].keys()) > 1:
1625                WriteCIFitem('\t_pd_refln_phase_id')
1626            WriteCIFitem('\t' + refprx + 'index_h' + 
1627                         '\n\t' + refprx + 'index_k' + 
1628                         '\n\t' + refprx + 'index_l' + 
1629                         '\n\t' + refprx + 'F_squared_meas' + 
1630                         '\n\t' + refprx + 'F_squared_calc' + 
1631                         '\n\t' + refprx + 'phase_calc' + 
1632                         '\n\t_pd_refln_d_spacing')
1633            if Imax > 0:
1634                WriteCIFitem('\t_gsas_i100_meas')
1635
1636            refcount = 0
1637            hklmin = None
1638            hklmax = None
1639            dmax = None
1640            dmin = None
1641            for phasenam in histblk['Reflection Lists']:
1642                scale = self.Phases[phasenam]['Histograms'][histlbl]['Scale'][0]
1643                phaseid = self.Phases[phasenam]['pId']
1644                refcount += len(histblk['Reflection Lists'][phasenam])
1645                for ref in histblk['Reflection Lists'][phasenam]:
1646                    if DEBUG:
1647                        print('DEBUG: skipping reflection list')
1648                        break
1649                    if hklmin is None:
1650                        hklmin = ref[0:3]
1651                        hklmax = ref[0:3]
1652                        dmax = dmin = ref[4]
1653                    if len(histblk['Reflection Lists'].keys()) > 1:
1654                        s = PutInCol(phaseid,2)
1655                    else:
1656                        s = ""
1657                    for i,hkl in enumerate(ref[0:3]):
1658                        hklmax[i] = max(hkl,hklmax[i])
1659                        hklmin[i] = min(hkl,hklmin[i])
1660                        s += PutInCol(int(hkl),4)
1661                    for I in ref[8:10]:
1662                        s += PutInCol(G2mth.ValEsd(I,-0.0009),10)
1663                    s += PutInCol(G2mth.ValEsd(ref[10],-0.9),7)
1664                    dmax = max(dmax,ref[4])
1665                    dmin = min(dmin,ref[4])
1666                    s += PutInCol(G2mth.ValEsd(ref[4],-0.009),8)
1667                    if Imax > 0:
1668                        I100 = 100.*scale*ref[8]*ref[13]/Imax
1669                        s += PutInCol(G2mth.ValEsd(I100,-0.09),6)
1670                    WriteCIFitem("  "+s)
1671
1672            WriteReflStat(refcount,hklmin,hklmax,dmin,dmax,len(histblk['Reflection Lists']))
1673            WriteCIFitem('\n# POWDER DATA TABLE')
1674            # is data fixed step? If the step varies by <0.01% treat as fixed step
1675            steps = histblk['Data'][0][1:] - histblk['Data'][0][:-1]
1676            if abs(max(steps)-min(steps)) > abs(max(steps))/10000.:
1677                fixedstep = False
1678            else:
1679                fixedstep = True
1680
1681            if fixedstep: # and not TOF
1682                WriteCIFitem('_pd_meas_2theta_range_min', G2mth.ValEsd(histblk['Data'][0][0],-0.00009))
1683                WriteCIFitem('_pd_meas_2theta_range_max', G2mth.ValEsd(histblk['Data'][0][-1],-0.00009))
1684                WriteCIFitem('_pd_meas_2theta_range_inc', G2mth.ValEsd(steps.sum()/len(steps),-0.00009))
1685                # zero correct, if defined
1686                zero = None
1687                zerolst = histblk['Instrument Parameters'][0].get('Zero')
1688                if zerolst: zero = zerolst[1]
1689                zero = self.parmDict.get('Zero',zero)
1690                if zero:
1691                    WriteCIFitem('_pd_proc_2theta_range_min', G2mth.ValEsd(histblk['Data'][0][0]-zero,-0.00009))
1692                    WriteCIFitem('_pd_proc_2theta_range_max', G2mth.ValEsd(histblk['Data'][0][-1]-zero,-0.00009))
1693                    WriteCIFitem('_pd_proc_2theta_range_inc', G2mth.ValEsd(steps.sum()/len(steps),-0.00009))
1694               
1695            if zero:
1696                WriteCIFitem('_pd_proc_number_of_points', str(len(histblk['Data'][0])))
1697            else:
1698                WriteCIFitem('_pd_meas_number_of_points', str(len(histblk['Data'][0])))
1699            WriteCIFitem('\nloop_')
1700            #            WriteCIFitem('\t_pd_proc_d_spacing') # need easy way to get this
1701            if not fixedstep:
1702                if zero:
1703                    WriteCIFitem('\t_pd_proc_2theta_corrected')
1704                else:
1705                    WriteCIFitem('\t_pd_meas_2theta_scan')
1706            # at least for now, always report weights.
1707            #if countsdata:
1708            #    WriteCIFitem('\t_pd_meas_counts_total')
1709            #else:
1710            WriteCIFitem('\t_pd_meas_intensity_total')
1711            WriteCIFitem('\t_pd_calc_intensity_total')
1712            WriteCIFitem('\t_pd_proc_intensity_bkg_calc')
1713            WriteCIFitem('\t_pd_proc_ls_weight')
1714            maxY = max(histblk['Data'][1].max(),histblk['Data'][3].max())
1715            if maxY < 0: maxY *= -10 # this should never happen, but...
1716            ndec = max(0,10-int(np.log10(maxY))-1) # 10 sig figs should be enough
1717            maxSU = histblk['Data'][2].max()
1718            if maxSU < 0: maxSU *= -1 # this should never happen, but...
1719            ndecSU = max(0,8-int(np.log10(maxSU))-1) # 8 sig figs should be enough
1720            lowlim,highlim = histblk['Limits'][1]
1721
1722            if DEBUG:
1723                print('DEBUG: skipping profile list')
1724            else:   
1725                for x,yobs,yw,ycalc,ybkg in zip(histblk['Data'][0],
1726                                                histblk['Data'][1],
1727                                                histblk['Data'][2],
1728                                                histblk['Data'][3],
1729                                                histblk['Data'][4]):
1730                    if lowlim <= x <= highlim:
1731                        pass
1732                    else:
1733                        yw = 0.0 # show the point is not in use
1734   
1735                    if fixedstep:
1736                        s = ""
1737                    else:
1738                        s = PutInCol(G2mth.ValEsd(x-zero,-0.00009),10)
1739                    s += PutInCol(Yfmt(ndec,yobs),12)
1740                    s += PutInCol(Yfmt(ndec,ycalc),12)
1741                    s += PutInCol(Yfmt(ndec,ybkg),11)
1742                    s += PutInCol(Yfmt(ndecSU,yw),9)
1743                    WriteCIFitem("  "+s)
1744
1745        def WriteSingleXtalData(histlbl):
1746            histblk = self.Histograms[histlbl]
1747            #refprx = '_refln.' # mm
1748            refprx = '_refln_' # normal
1749
1750            WriteCIFitem('\n# STRUCTURE FACTOR TABLE')           
1751            WriteCIFitem('loop_' + 
1752                         '\n\t' + refprx + 'index_h' + 
1753                         '\n\t' + refprx + 'index_k' + 
1754                         '\n\t' + refprx + 'index_l' +
1755                         '\n\t' + refprx + 'F_squared_meas' + 
1756                         '\n\t' + refprx + 'F_squared_sigma' + 
1757                         '\n\t' + refprx + 'F_squared_calc' + 
1758                         '\n\t' + refprx + 'phase_calc'
1759                         )
1760
1761            hklmin = None
1762            hklmax = None
1763            dmax = None
1764            dmin = None
1765            refcount = len(histblk['Data'])
1766            for ref in histblk['Data']:
1767                s = "  "
1768                if hklmin is None:
1769                    hklmin = ref[0:3]
1770                    hklmax = ref[0:3]
1771                    dmax = dmin = ref[4]
1772                for i,hkl in enumerate(ref[0:3]):
1773                    hklmax[i] = max(hkl,hklmax[i])
1774                    hklmin[i] = min(hkl,hklmin[i])
1775                    s += PutInCol(int(hkl),4)
1776                sig = ref[6] * ref[8] / ref[5]
1777                s += PutInCol(G2mth.ValEsd(ref[8],-abs(sig/10)),12)
1778                s += PutInCol(G2mth.ValEsd(sig,-abs(sig)/10.),10)
1779                s += PutInCol(G2mth.ValEsd(ref[9],-abs(sig/10)),12)
1780                s += PutInCol(G2mth.ValEsd(ref[10],-0.9),7)
1781                dmax = max(dmax,ref[4])
1782                dmin = min(dmin,ref[4])
1783                WriteCIFitem(s)
1784            WriteReflStat(refcount,hklmin,hklmax,dmin,dmax)
1785            hId = histblk['hId']
1786            pfx = '0:'+str(hId)+':'
1787            WriteCIFitem('_reflns_wR_factor_obs    ','%.4f'%(histblk['wR']/100.))
1788            WriteCIFitem('_reflns_R_F_factor_obs   ','%.4f'%(histblk[pfx+'Rf']/100.))
1789            WriteCIFitem('_reflns_R_Fsqd_factor_obs','%.4f'%(histblk[pfx+'Rf^2']/100.))
1790        def EditAuthor(event=None):
1791            'Edit the CIF author name'
1792            dlg = G2gd.SingleStringDialog(self.G2frame,
1793                                          'Get CIF Author',
1794                                          'Provide CIF Author name (Last, First)',
1795                                          value=self.author)
1796            if not dlg.Show():
1797                dlg.Destroy()
1798                return False  # cancel was pressed
1799            self.author = dlg.GetValue()
1800            dlg.Destroy()
1801            try:
1802                self.OverallParms['Controls']["Author"] = self.author # save for future
1803            except KeyError:
1804                pass
1805            return True
1806        def EditInstNames(event=None):
1807            'Provide a dialog for editing instrument names'
1808            dictlist = []
1809            keylist = []
1810            lbllist = []
1811            for hist in self.Histograms:
1812                if hist.startswith("PWDR"): 
1813                    key2 = "Sample Parameters"
1814                    d = self.Histograms[hist][key2]
1815                elif hist.startswith("HKLF"): 
1816                    key2 = "Instrument Parameters"
1817                    d = self.Histograms[hist][key2][0]
1818                   
1819                lbllist.append(hist)
1820                dictlist.append(d)
1821                keylist.append('InstrName')
1822                instrname = d.get('InstrName')
1823                if instrname is None:
1824                    d['InstrName'] = ''
1825            return G2gd.CallScrolledMultiEditor(
1826                self.G2frame,dictlist,keylist,
1827                prelbl=range(1,len(dictlist)+1),
1828                postlbl=lbllist,
1829                title='Instrument names',
1830                header="Edit instrument names. Note that a non-blank\nname is required for all histograms",
1831                CopyButton=True)
1832           
1833        def EditRanges(event):
1834            but = event.GetEventObject()
1835            phasedict = but.phasedict
1836            dlg = G2gd.DisAglDialog(self.G2frame,{},phasedict['General'])
1837            if dlg.ShowModal() == wx.ID_OK:
1838                phasedict['General']['DisAglCtls'] = dlg.GetData()
1839            dlg.Destroy()
1840           
1841        def EditCIFDefaults():
1842            'Fills the CIF Defaults window'
1843            import wx.lib.scrolledpanel as wxscroll
1844            self.cifdefs.DestroyChildren()
1845            self.cifdefs.SetTitle('Edit CIF settings')
1846            vbox = wx.BoxSizer(wx.VERTICAL)
1847            but = wx.Button(self.cifdefs, wx.ID_ANY,'Edit CIF Author')
1848            but.Bind(wx.EVT_BUTTON,EditAuthor)
1849            vbox.Add(but,0,wx.ALIGN_CENTER,3)
1850            but = wx.Button(self.cifdefs, wx.ID_ANY,'Edit Instrument Name(s)')
1851            but.Bind(wx.EVT_BUTTON,EditInstNames)
1852            vbox.Add(but,0,wx.ALIGN_CENTER,3)
1853            cpnl = wxscroll.ScrolledPanel(self.cifdefs,size=(300,300))
1854            cbox = wx.BoxSizer(wx.VERTICAL)
1855            G2gd.HorizontalLine(cbox,cpnl)         
1856            cbox.Add(
1857                CIFtemplateSelect(self.cifdefs,
1858                                  cpnl,'publ',self.OverallParms['Controls'],
1859                                  EditCIFDefaults,
1860                                  "Publication (overall) template",
1861                                  ),
1862                0,wx.EXPAND|wx.ALIGN_LEFT|wx.ALL)
1863            for phasenam in sorted(self.Phases.keys()):
1864                G2gd.HorizontalLine(cbox,cpnl)         
1865                title = 'Phase '+phasenam
1866                phasedict = self.Phases[phasenam] # pointer to current phase info           
1867                cbox.Add(
1868                    CIFtemplateSelect(self.cifdefs,
1869                                      cpnl,'phase',phasedict['General'],
1870                                      EditCIFDefaults,
1871                                      title,
1872                                      phasenam),
1873                    0,wx.EXPAND|wx.ALIGN_LEFT|wx.ALL)
1874                cpnl.SetSizer(cbox)
1875                but = wx.Button(cpnl, wx.ID_ANY,'Edit distance/angle ranges')
1876                #cbox.Add(but,0,wx.ALIGN_CENTER,3)
1877                cbox.Add((-1,2))
1878                cbox.Add(but,0,wx.ALIGN_LEFT,0)
1879                but.phasedict = self.Phases[phasenam]  # set a pointer to current phase info     
1880                but.Bind(wx.EVT_BUTTON,EditRanges)     # phase bond/angle ranges
1881            for i in sorted(self.powderDict.keys()):
1882                G2gd.HorizontalLine(cbox,cpnl)         
1883                hist = self.powderDict[i]
1884                histblk = self.Histograms[hist]
1885                title = 'Powder dataset '+hist[5:]
1886                cbox.Add(
1887                    CIFtemplateSelect(self.cifdefs,
1888                                      cpnl,'powder',histblk["Sample Parameters"],
1889                                      EditCIFDefaults,
1890                                      title,
1891                                      histblk["Sample Parameters"]['InstrName']),
1892                    0,wx.EXPAND|wx.ALIGN_LEFT|wx.ALL)
1893                cpnl.SetSizer(cbox)
1894            for i in sorted(self.xtalDict.keys()):
1895                G2gd.HorizontalLine(cbox,cpnl)         
1896                hist = self.xtalDict[i]
1897                histblk = self.Histograms[hist]
1898                title = 'Single Xtal dataset '+hist[5:]
1899                cbox.Add(
1900                    CIFtemplateSelect(self.cifdefs,
1901                                      cpnl,'single',histblk["Instrument Parameters"][0],
1902                                      EditCIFDefaults,
1903                                      title,
1904                                      histblk["Instrument Parameters"][0]['InstrName']),
1905                    0,wx.EXPAND|wx.ALIGN_LEFT|wx.ALL)
1906                cpnl.SetSizer(cbox)
1907
1908            cpnl.SetAutoLayout(1)
1909            cpnl.SetupScrolling()
1910            #cpnl.Bind(rw.EVT_RW_LAYOUT_NEEDED, self.OnLayoutNeeded) # needed if sizes change
1911            cpnl.Layout()
1912
1913            vbox.Add(cpnl, 1, wx.ALIGN_LEFT|wx.ALL|wx.EXPAND, 0)
1914            btnsizer = wx.StdDialogButtonSizer()
1915            btn = wx.Button(self.cifdefs, wx.ID_OK, "Create CIF")
1916            btn.SetDefault()
1917            btnsizer.AddButton(btn)
1918            btn = wx.Button(self.cifdefs, wx.ID_CANCEL)
1919            btnsizer.AddButton(btn)
1920            btnsizer.Realize()
1921            vbox.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5)
1922            self.cifdefs.SetSizer(vbox)
1923            vbox.Fit(self.cifdefs)
1924            self.cifdefs.Layout()
1925
1926# ===== end of functions for export method =======================================
1927#=================================================================================
1928
1929        # the export process starts here
1930        # load all of the tree into a set of dicts
1931        self.loadTree()
1932        # create a dict with refined values and their uncertainties
1933        self.loadParmDict()
1934
1935        # Someday: get restraint & constraint info
1936        #restraintDict = self.OverallParms.get('Restraints',{})
1937        #for i in  self.OverallParms['Constraints']:
1938        #    print i
1939        #    for j in self.OverallParms['Constraints'][i]:
1940        #        print j
1941
1942        self.CIFdate = dt.datetime.strftime(dt.datetime.now(),"%Y-%m-%dT%H:%M")
1943        # index powder and single crystal histograms
1944        self.powderDict = {}
1945        self.xtalDict = {}
1946        for hist in self.Histograms:
1947            i = self.Histograms[hist]['hId']
1948            if hist.startswith("PWDR"): 
1949                self.powderDict[i] = hist
1950            elif hist.startswith("HKLF"): 
1951                self.xtalDict[i] = hist
1952        # is there anything to export?
1953        if len(self.Phases) == len(self.powderDict) == len(self.xtalDict) == 0:
1954           self.G2frame.ErrorDialog(
1955               'Empty project',
1956               'Project does not contain interconnected data & phase(s)')
1957           return
1958        # get the project file name
1959        self.CIFname = os.path.splitext(
1960            os.path.split(self.G2frame.GSASprojectfile)[1]
1961            )[0]
1962        self.CIFname = self.CIFname.replace(' ','')
1963        if not self.CIFname: # none defined & needed, save as GPX to get one
1964            self.G2frame.OnFileSaveas(None)
1965            if not self.G2frame.GSASprojectfile: return
1966            self.CIFname = os.path.splitext(
1967                os.path.split(self.G2frame.GSASprojectfile)[1]
1968                )[0]
1969            self.CIFname = self.CIFname.replace(' ','')
1970        # test for quick CIF mode or no data
1971        self.quickmode = False
1972        phasenam = phasenum = None # include all phases
1973        if mode != "full" or len(self.powderDict) + len(self.xtalDict) == 0:
1974            self.quickmode = True
1975            oneblock = True
1976            if len(self.Phases) == 0:
1977                self.G2frame.ErrorDialog(
1978                    'No phase present',
1979                    'Cannot create a coordinates CIF with no phases')
1980                return
1981            elif len(self.Phases) > 1: # quick mode: choose one phase
1982                choices = sorted(self.Phases.keys())
1983                phasenum = G2gd.ItemSelector(choices,self.G2frame)
1984                if phasenum is None: return
1985                phasenam = choices[phasenum]
1986        # will this require a multiblock CIF?
1987        elif len(self.Phases) > 1:
1988            oneblock = False
1989        elif len(self.powderDict) + len(self.xtalDict) > 1:
1990            oneblock = False
1991        else: # one phase, one dataset, Full CIF
1992            oneblock = True
1993
1994        # make sure needed infomation is present
1995        # get CIF author name -- required for full CIFs
1996        try:
1997            self.author = self.OverallParms['Controls'].get("Author",'').strip()
1998        except KeyError:
1999            pass
2000        while not (self.author or self.quickmode):
2001            if not EditAuthor(): return
2002        self.shortauthorname = self.author.replace(',','').replace(' ','')[:20]
2003
2004        # check there is an instrument name for every histogram
2005        if not self.quickmode:
2006            invalid = 0
2007            key3 = 'InstrName'
2008            for hist in self.Histograms:
2009                if hist.startswith("PWDR"): 
2010                    key2 = "Sample Parameters"
2011                    d = self.Histograms[hist][key2]
2012                elif hist.startswith("HKLF"): 
2013                    key2 = "Instrument Parameters"
2014                    d = self.Histograms[hist][key2][0]                   
2015                instrname = d.get(key3)
2016                if instrname is None:
2017                    d[key3] = ''
2018                    invalid += 1
2019                elif instrname.strip() == '':
2020                    invalid += 1
2021            if invalid:
2022                msg = ""
2023                if invalid > 3: msg = (
2024                    "\n\nNote: it may be faster to set the name for\n"
2025                    "one histogram for each instrument and use the\n"
2026                    "File/Copy option to duplicate the name"
2027                    )
2028                if not EditInstNames(): return
2029        # check for a distance-angle range search range for each phase
2030        if not self.quickmode:
2031            for phasenam in sorted(self.Phases.keys()):
2032                #i = self.Phases[phasenam]['pId']
2033                phasedict = self.Phases[phasenam] # pointer to current phase info           
2034                if 'DisAglCtls' not in phasedict['General']:
2035                    dlg = G2gd.DisAglDialog(self.G2frame,{},phasedict['General'])
2036                    if dlg.ShowModal() == wx.ID_OK:
2037                        phasedict['General']['DisAglCtls'] = dlg.GetData()
2038                    else:
2039                        dlg.Destroy()
2040                        return
2041                    dlg.Destroy()
2042
2043        if oneblock and not self.quickmode:
2044            # select a dataset to use (there should only be one set in one block,
2045            # but take whatever comes 1st)
2046            for hist in self.Histograms:
2047                histblk = self.Histograms[hist]
2048                if hist.startswith("PWDR"): 
2049                    instnam = histblk["Sample Parameters"]['InstrName']
2050                    break # ignore all but 1st data histogram
2051                elif hist.startswith("HKLF"): 
2052                    instnam = histblk["Instrument Parameters"][0]['InstrName']
2053                    break # ignore all but 1st data histogram
2054        if self.quickmode:
2055            fil = self.askSaveFile()
2056        else:
2057            fil = self.defSaveFile()
2058        if not fil: return
2059        if not self.quickmode: # give the user a chance to edit all defaults
2060            self.cifdefs = wx.Dialog(
2061                self.G2frame,style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
2062            EditCIFDefaults()
2063            val = self.cifdefs.ShowModal()
2064            self.cifdefs.Destroy()
2065            if val != wx.ID_OK:
2066                return
2067        #======================================================================
2068        # Start writing the CIF - single block
2069        #======================================================================
2070        openCIF(fil)
2071        if oneblock:
2072            WriteCIFitem('data_'+self.CIFname)
2073            if phasenam is None: # if not already selected, select the first phase (should be one)
2074                phasenam = self.Phases.keys()[0]
2075            #print 'phasenam',phasenam
2076            phaseblk = self.Phases[phasenam] # pointer to current phase info
2077            if not self.quickmode:
2078                instnam = instnam.replace(' ','')
2079                WriteCIFitem('_pd_block_id',
2080                             str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
2081                             str(self.shortauthorname) + "|" + instnam)
2082                WriteAudit()
2083                WritePubTemplate()
2084                WriteOverall()
2085                WritePhaseTemplate(phasenam)
2086            # report the phase info
2087            WritePhaseInfo(phasenam)
2088            if hist.startswith("PWDR") and not self.quickmode:
2089                # preferred orientation
2090                SH = FormatSH(phasenam)
2091                MD = FormatHAPpo(phasenam)
2092                if SH and MD:
2093                    WriteCIFitem('_pd_proc_ls_pref_orient_corr', SH + '\n' + MD)
2094                elif SH or MD:
2095                    WriteCIFitem('_pd_proc_ls_pref_orient_corr', SH + MD)
2096                else:
2097                    WriteCIFitem('_pd_proc_ls_pref_orient_corr', 'none')
2098                    # report profile, since one-block: include both histogram and phase info
2099                WriteCIFitem('_pd_proc_ls_profile_function',
2100                    FormatInstProfile(histblk["Instrument Parameters"],histblk['hId'])
2101                    +'\n'+FormatPhaseProfile(phasenam))
2102                WritePowderTemplate(hist)
2103                WritePowderData(hist)
2104            elif hist.startswith("HKLF") and not self.quickmode:
2105                WriteSnglXtalTemplate(hist)
2106                WriteSingleXtalData(hist)
2107        else:
2108        #======================================================================
2109        # Start writing the CIF - multiblock
2110        #======================================================================
2111            # publication info
2112            WriteCIFitem('\ndata_'+self.CIFname+'_publ')
2113            WriteAudit()
2114            WriteCIFitem('_pd_block_id',
2115                         str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
2116                         str(self.shortauthorname) + "|Overall")
2117            WritePubTemplate()
2118            # overall info
2119            WriteCIFitem('data_'+str(self.CIFname)+'_overall')
2120            WriteOverall()
2121            #============================================================
2122            WriteCIFitem('# POINTERS TO PHASE AND HISTOGRAM BLOCKS')
2123            datablockidDict = {} # save block names here -- N.B. check for conflicts between phase & hist names (unlikely!)
2124            # loop over phase blocks
2125            if len(self.Phases) > 1:
2126                loopprefix = ''
2127                WriteCIFitem('loop_   _pd_phase_block_id')
2128            else:
2129                loopprefix = '_pd_phase_block_id'
2130           
2131            for phasenam in sorted(self.Phases.keys()):
2132                i = self.Phases[phasenam]['pId']
2133                datablockidDict[phasenam] = (str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
2134                             'phase_'+ str(i) + '|' + str(self.shortauthorname))
2135                WriteCIFitem(loopprefix,datablockidDict[phasenam])
2136            # loop over data blocks
2137            if len(self.powderDict) + len(self.xtalDict) > 1:
2138                loopprefix = ''
2139                WriteCIFitem('loop_   _pd_block_diffractogram_id')
2140            else:
2141                loopprefix = '_pd_block_diffractogram_id'
2142            for i in sorted(self.powderDict.keys()):
2143                hist = self.powderDict[i]
2144                histblk = self.Histograms[hist]
2145                instnam = histblk["Sample Parameters"]['InstrName']
2146                instnam = instnam.replace(' ','')
2147                i = histblk['hId']
2148                datablockidDict[hist] = (str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
2149                                         str(self.shortauthorname) + "|" +
2150                                         instnam + "_hist_"+str(i))
2151                WriteCIFitem(loopprefix,datablockidDict[hist])
2152            for i in sorted(self.xtalDict.keys()):
2153                hist = self.xtalDict[i]
2154                histblk = self.Histograms[hist]
2155                instnam = histblk["Instrument Parameters"][0]['InstrName']
2156                instnam = instnam.replace(' ','')
2157                i = histblk['hId']
2158                datablockidDict[hist] = (str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
2159                                         str(self.shortauthorname) + "|" +
2160                                         instnam + "_hist_"+str(i))
2161                WriteCIFitem(loopprefix,datablockidDict[hist])
2162            #============================================================
2163            # loop over phases, exporting them
2164            phasebyhistDict = {} # create a cross-reference to phases by histogram
2165            for j,phasenam in enumerate(sorted(self.Phases.keys())):
2166                i = self.Phases[phasenam]['pId']
2167                WriteCIFitem('\ndata_'+self.CIFname+"_phase_"+str(i))
2168                WriteCIFitem('# Information for phase '+str(i))
2169                WriteCIFitem('_pd_block_id',datablockidDict[phasenam])
2170                # report the phase
2171                WritePhaseTemplate(phasenam)
2172                WritePhaseInfo(phasenam)
2173                # preferred orientation
2174                SH = FormatSH(phasenam)
2175                MD = FormatHAPpo(phasenam)
2176                if SH and MD:
2177                    WriteCIFitem('_pd_proc_ls_pref_orient_corr', SH + '\n' + MD)
2178                elif SH or MD:
2179                    WriteCIFitem('_pd_proc_ls_pref_orient_corr', SH + MD)
2180                else:
2181                    WriteCIFitem('_pd_proc_ls_pref_orient_corr', 'none')
2182                # report sample profile terms
2183                PP = FormatPhaseProfile(phasenam)
2184                if PP:
2185                    WriteCIFitem('_pd_proc_ls_profile_function',PP)
2186                   
2187            #============================================================
2188            # loop over histograms, exporting them
2189            for i in sorted(self.powderDict.keys()):
2190                hist = self.powderDict[i]
2191                histblk = self.Histograms[hist]
2192                if hist.startswith("PWDR"): 
2193                    WriteCIFitem('\ndata_'+self.CIFname+"_pwd_"+str(i))
2194                    #instnam = histblk["Sample Parameters"]['InstrName']
2195                    # report instrumental profile terms
2196                    WriteCIFitem('_pd_proc_ls_profile_function',
2197                        FormatInstProfile(histblk["Instrument Parameters"],histblk['hId']))
2198                    WriteCIFitem('# Information for histogram '+str(i)+': '+hist)
2199                    WriteCIFitem('_pd_block_id',datablockidDict[hist])
2200                    WritePowderTemplate(hist)
2201                    WritePowderData(hist)
2202            for i in sorted(self.xtalDict.keys()):
2203                hist = self.xtalDict[i]
2204                histblk = self.Histograms[hist]
2205                if hist.startswith("HKLF"): 
2206                    WriteCIFitem('\ndata_'+self.CIFname+"_sx_"+str(i))
2207                    #instnam = histblk["Instrument Parameters"][0]['InstrName']
2208                    WriteCIFitem('# Information for histogram '+str(i)+': '+hist)
2209                    WriteCIFitem('_pd_block_id',datablockidDict[hist])
2210                    WriteSnglXtalTemplate(hist)
2211                    WriteSingleXtalData(hist)
2212
2213        WriteCIFitem('#--' + 15*'eof--' + '#')
2214        closeCIF()
Note: See TracBrowser for help on using the repository browser.