source: trunk/exports/G2cif.py @ 1069

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

finally a working CIF exporter (export mechanism needs configuring); work around windows file names in PyCifRW & missing unicode char

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