source: trunk/GSASIIIO.py @ 4198

Last change on this file since 4198 was 4198, checked in by toby, 23 months ago

doc improvements; new scripting options (rev. sequential & set background file)

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Author Revision URL Id
File size: 93.8 KB
Line 
1# -*- coding: utf-8 -*-
2########### SVN repository information ###################
3# $Date: 2019-12-09 20:30:49 +0000 (Mon, 09 Dec 2019) $
4# $Author: toby $
5# $Revision: 4198 $
6# $URL: trunk/GSASIIIO.py $
7# $Id: GSASIIIO.py 4198 2019-12-09 20:30:49Z toby $
8########### SVN repository information ###################
9'''
10*GSASIIIO: Misc I/O routines*
11=============================
12
13Module with miscellaneous routines for input and output. Many
14are GUI routines to interact with user.
15
16Includes support for image reading.
17
18Also includes base classes for data import routines.
19
20This module needs some work to separate wx from non-wx routines
21'''
22# If this is being used for GSASIIscriptable, don't depend on wx
23from __future__ import division, print_function
24try:
25    import wx
26except ImportError:
27    class Placeholder(object):
28        def __init__(self):
29            self.Dialog = object
30    wx = Placeholder()
31import math
32import numpy as np
33import numpy.ma as ma
34
35import copy
36import platform
37if '2' in platform.python_version_tuple()[0]:
38    import cPickle
39else:
40    import pickle as cPickle
41import sys
42import re
43import glob
44import random as ran
45import GSASIIpath
46GSASIIpath.SetVersionNumber("$Revision: 4198 $")
47try:
48    import GSASIIdataGUI as G2gd
49except ImportError:
50    pass
51import GSASIIobj as G2obj
52import GSASIIlattice as G2lat
53try:
54    import GSASIIpwdGUI as G2pdG
55    import GSASIIimgGUI as G2imG
56except ImportError:
57    pass
58import GSASIIimage as G2img
59import GSASIIElem as G2el
60import GSASIIstrIO as G2stIO
61import GSASIImapvars as G2mv
62import GSASIIfiles as G2fil
63try:
64    import GSASIIctrlGUI as G2G
65except ImportError:
66    pass
67import os
68import os.path as ospath
69
70DEBUG = False       #=True for various prints
71TRANSP = False      #=true to transpose images for testing
72if GSASIIpath.GetConfigValue('Transpose'): TRANSP = True
73npsind = lambda x: np.sin(x*np.pi/180.)
74
75def sfloat(S):
76    'Convert a string to float. An empty field or a unconvertable value is treated as zero'
77    if S.strip():
78        try:
79            return float(S)
80        except ValueError:
81            pass
82    return 0.0
83
84def sint(S):
85    'Convert a string to int. An empty field is treated as zero'
86    if S.strip():
87        return int(S)
88    else:
89        return 0
90
91def trim(val):
92    '''Simplify a string containing leading and trailing spaces
93    as well as newlines, tabs, repeated spaces etc. into a shorter and
94    more simple string, by replacing all ranges of whitespace
95    characters with a single space.
96
97    :param str val: the string to be simplified
98
99    :returns: the (usually) shortened version of the string
100    '''
101    return re.sub('\s+', ' ', val).strip()
102
103def FileDlgFixExt(dlg,file):
104    'this is needed to fix a problem in linux wx.FileDialog'
105    ext = dlg.GetWildcard().split('|')[2*dlg.GetFilterIndex()+1].strip('*')
106    if ext not in file:
107        file += ext
108    return file
109       
110def GetPowderPeaks(fileName):
111    'Read powder peaks from a file'
112    sind = lambda x: math.sin(x*math.pi/180.)
113    asind = lambda x: 180.*math.asin(x)/math.pi
114    wave = 1.54052
115    File = open(fileName,'Ur')
116    Comments = []
117    peaks = []
118    S = File.readline()
119    while S:
120        if S[:1] == '#':
121            Comments.append(S[:-1])
122        else:
123            item = S.split()
124            if len(item) == 1:
125                peaks.append([float(item[0]),1.0])
126            elif len(item) > 1:
127                peaks.append([float(item[0]),float(item[0])])
128        S = File.readline()
129    File.close()
130    if Comments:
131       print ('Comments on file:')
132       for Comment in Comments: 
133            print (Comment)
134            if 'wavelength' in Comment:
135                wave = float(Comment.split('=')[1])
136    Peaks = []
137    if peaks[0][0] > peaks[-1][0]:          # d-spacings - assume CuKa
138        for peak in peaks:
139            dsp = peak[0]
140            sth = wave/(2.0*dsp)
141            if sth < 1.0:
142                tth = 2.0*asind(sth)
143            else:
144                break
145            Peaks.append([tth,peak[1],True,False,0,0,0,dsp,0.0])
146    else:                                   #2-thetas - assume Cuka (for now)
147        for peak in peaks:
148            tth = peak[0]
149            dsp = wave/(2.0*sind(tth/2.0))
150            Peaks.append([tth,peak[1],True,False,0,0,0,dsp,0.0])
151    limits = [1000.,0.]
152    for peak in Peaks:
153        limits[0] = min(limits[0],peak[0])
154        limits[1] = max(limits[1],peak[0])
155    limits[0] = max(1.,(int(limits[0]-1.)/5)*5.)
156    limits[1] = min(170.,(int(limits[1]+1.)/5)*5.)
157    return Comments,Peaks,limits,wave
158
159def GetCheckImageFile(G2frame,treeId):
160    '''Try to locate an image file if the project and image have been moved
161    together. If the image file cannot be found, request the location from
162    the user.
163
164    :param wx.Frame G2frame: main GSAS-II Frame and data object
165    :param wx.Id treeId: Id for the main tree item for the image
166    :returns: Npix,imagefile,imagetag with (Npix) number of pixels,
167       imagefile, if it exists, or the name of a file that does exist or False if the user presses Cancel
168       and (imagetag) an optional image number
169
170    '''
171    Npix,Imagefile,imagetag = G2frame.GPXtree.GetImageLoc(treeId)
172    if isinstance(Imagefile,list):
173        imagefile,imagetag = Imagefile
174    else:
175        imagefile = Imagefile
176    if not os.path.exists(imagefile):
177        print ('Image file '+imagefile+' not found')
178        fil = imagefile.replace('\\','/') # windows?!
179        # see if we can find a file with same name or in a similarly named sub-dir
180        pth,fil = os.path.split(fil)
181        prevpth = None
182        while pth and pth != prevpth:
183            prevpth = pth
184            if os.path.exists(os.path.join(G2frame.dirname,fil)):
185                print ('found image file '+os.path.join(G2frame.dirname,fil))
186                imagefile = os.path.join(G2frame.dirname,fil)
187                G2frame.GPXtree.UpdateImageLoc(treeId,imagefile)
188                return Npix,imagefile,imagetag
189            pth,enddir = os.path.split(pth)
190            fil = os.path.join(enddir,fil)
191        # not found as a subdirectory, drop common parts of path for last saved & image file names
192        #    if image was .../A/B/C/imgs/ima.ge
193        #      & GPX was  .../A/B/C/refs/fil.gpx but is now .../NEW/TEST/TEST1
194        #    will look for .../NEW/TEST/TEST1/imgs/ima.ge, .../NEW/TEST/imgs/ima.ge, .../NEW/imgs/ima.ge and so on
195        Controls = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,G2frame.root, 'Controls'))
196        gpxPath = Controls.get('LastSavedAs','').replace('\\','/').split('/') # blank in older .GPX files
197        imgPath = imagefile.replace('\\','/').split('/')
198        for p1,p2 in zip(gpxPath,imgPath):
199            if p1 == p2:
200                gpxPath.pop(0),imgPath.pop(0)
201            else:
202                break
203        fil = os.path.join(*imgPath) # file with non-common prefix elements
204        prevpth = None
205        pth = os.path.abspath(G2frame.dirname)
206        while pth and pth != prevpth:
207            prevpth = pth
208            if os.path.exists(os.path.join(pth,fil)):
209                print ('found image file '+os.path.join(pth,fil))
210                imagefile = os.path.join(pth,fil)
211                G2frame.GPXtree.UpdateImageLoc(treeId,imagefile)
212                return Npix,imagefile,imagetag
213            pth,enddir = os.path.split(pth)
214        #GSASIIpath.IPyBreak()
215
216    if not os.path.exists(imagefile):
217        # note that this fails (at least on Mac) to get an image during the GUI initialization
218        prevnam = os.path.split(imagefile)[1]
219        prevext = os.path.splitext(imagefile)[1]
220        wildcard = 'Image format (*'+prevext+')|*'+prevext
221        dlg = wx.FileDialog(G2frame, 'Previous image file ('+prevnam+') not found; open here', '.', prevnam,
222                            wildcard,wx.FD_OPEN)
223        try:
224            dlg.SetFilename(''+ospath.split(imagefile)[1])
225            if dlg.ShowModal() == wx.ID_OK:
226                imagefile = dlg.GetPath()
227                G2frame.GPXtree.UpdateImageLoc(treeId,imagefile)
228            else:
229                imagefile = None # was False
230        finally:
231            dlg.Destroy()
232    return Npix,imagefile,imagetag
233
234def EditImageParms(parent,Data,Comments,Image,filename):
235    dlg = wx.Dialog(parent, wx.ID_ANY, 'Edit image parameters',
236                    style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
237    def onClose(event):
238        dlg.EndModal(wx.ID_OK)
239    mainsizer = wx.BoxSizer(wx.VERTICAL)
240    h,w = Image.shape[:2]
241    mainsizer.Add(wx.StaticText(dlg,wx.ID_ANY,'File '+str(filename)+'\nImage size: '+str(h)+' x '+str(w)),
242        0,wx.ALIGN_LEFT|wx.ALL, 2)
243   
244    vsizer = wx.BoxSizer(wx.HORIZONTAL)
245    vsizer.Add(wx.StaticText(dlg,wx.ID_ANY,u'Wavelength (\xC5) '),
246        0,wx.ALIGN_LEFT|wx.ALL, 2)
247    wdgt = G2G.ValidatedTxtCtrl(dlg,Data,'wavelength')
248    vsizer.Add(wdgt)
249    mainsizer.Add(vsizer,0,wx.ALIGN_LEFT|wx.ALL, 2)
250
251    vsizer = wx.BoxSizer(wx.HORIZONTAL)
252    vsizer.Add(wx.StaticText(dlg,wx.ID_ANY,u'Pixel size (\xb5m). Width '),
253        0,wx.ALIGN_LEFT|wx.ALL, 2)
254    wdgt = G2G.ValidatedTxtCtrl(dlg,Data['pixelSize'],0,
255                                 size=(50,-1))
256    vsizer.Add(wdgt)
257    vsizer.Add(wx.StaticText(dlg,wx.ID_ANY,u'  Height '),wx.ALIGN_LEFT|wx.ALL, 2)
258    wdgt = G2G.ValidatedTxtCtrl(dlg,Data['pixelSize'],1,size=(50,-1))
259    vsizer.Add(wdgt)
260    mainsizer.Add(vsizer,0,wx.ALIGN_LEFT|wx.ALL, 2)
261
262    vsizer = wx.BoxSizer(wx.HORIZONTAL)
263    vsizer.Add(wx.StaticText(dlg,wx.ID_ANY,u'Sample to detector (mm) '),
264        0,wx.ALIGN_LEFT|wx.ALL, 2)
265    wdgt = G2G.ValidatedTxtCtrl(dlg,Data,'distance')
266    vsizer.Add(wdgt)
267    mainsizer.Add(vsizer,0,wx.ALIGN_LEFT|wx.ALL, 2)
268
269    vsizer = wx.BoxSizer(wx.HORIZONTAL)
270    vsizer.Add(wx.StaticText(dlg,wx.ID_ANY,u'Beam center (pixels). X = '),
271        0,wx.ALIGN_LEFT|wx.ALL, 2)
272    wdgt = G2G.ValidatedTxtCtrl(dlg,Data['center'],0,size=(75,-1))
273    vsizer.Add(wdgt)
274    vsizer.Add(wx.StaticText(dlg,wx.ID_ANY,u'  Y = '),wx.ALIGN_LEFT|wx.ALL, 2)
275    wdgt = G2G.ValidatedTxtCtrl(dlg,Data['center'],1,size=(75,-1))
276    vsizer.Add(wdgt)
277    mainsizer.Add(vsizer,0,wx.ALIGN_LEFT|wx.ALL, 2)
278
279    vsizer = wx.BoxSizer(wx.HORIZONTAL)
280    vsizer.Add(wx.StaticText(dlg,wx.ID_ANY,u'Comments '),
281        0,wx.ALIGN_LEFT|wx.ALL, 2)
282    wdgt = G2G.ValidatedTxtCtrl(dlg,Comments,0,size=(250,-1))
283    vsizer.Add(wdgt)
284    mainsizer.Add(vsizer,0,wx.ALIGN_LEFT|wx.ALL, 2)
285
286    btnsizer = wx.StdDialogButtonSizer()
287    OKbtn = wx.Button(dlg, wx.ID_OK, 'Continue')
288    OKbtn.SetDefault()
289    OKbtn.Bind(wx.EVT_BUTTON,onClose)
290    btnsizer.AddButton(OKbtn) # not sure why this is needed
291    btnsizer.Realize()
292    mainsizer.Add(btnsizer, 1, wx.ALIGN_CENTER|wx.ALL|wx.EXPAND, 5)
293    dlg.SetSizer(mainsizer)
294    dlg.CenterOnParent()
295    dlg.ShowModal()
296   
297def LoadImage2Tree(imagefile,G2frame,Comments,Data,Npix,Image):
298    '''Load an image into the tree. Saves the location of the image, as well as the
299    ImageTag (where there is more than one image in the file), if defined.
300    '''
301    ImgNames = G2gd.GetGPXtreeDataNames(G2frame,['IMG ',])
302    TreeLbl = 'IMG '+os.path.basename(imagefile)
303    ImageTag = Data.get('ImageTag')
304    if ImageTag:
305        TreeLbl += ' #'+'%04d'%(ImageTag)
306        imageInfo = (imagefile,ImageTag)
307    else:
308        imageInfo = imagefile
309    TreeName = G2obj.MakeUniqueLabel(TreeLbl,ImgNames)
310    Id = G2frame.GPXtree.AppendItem(parent=G2frame.root,text=TreeName)
311    G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Comments'),Comments)
312    Imax = np.amax(Image)
313    if G2frame.imageDefault:
314        Data = copy.copy(G2frame.imageDefault)
315        Data['showLines'] = True
316        Data['ring'] = []
317        Data['rings'] = []
318        Data['cutoff'] = 10.
319        Data['pixLimit'] = 20
320        Data['edgemin'] = 100000000
321        Data['calibdmin'] = 0.5
322        Data['calibskip'] = 0
323        Data['ellipses'] = []
324        Data['calibrant'] = ''
325        Data['GonioAngles'] = [0.,0.,0.]
326        Data['DetDepthRef'] = False
327    else:
328        Data['type'] = 'PWDR'
329        Data['color'] = GSASIIpath.GetConfigValue('Contour_color','Paired')
330        if 'tilt' not in Data:          #defaults if not preset in e.g. Bruker importer
331            Data['tilt'] = 0.0
332            Data['rotation'] = 0.0
333            Data['pixLimit'] = 20
334            Data['calibdmin'] = 0.5
335            Data['cutoff'] = 10.
336        Data['showLines'] = False
337        Data['calibskip'] = 0
338        Data['ring'] = []
339        Data['rings'] = []
340        Data['edgemin'] = 100000000
341        Data['ellipses'] = []
342        Data['GonioAngles'] = [0.,0.,0.]
343        Data['DetDepth'] = 0.
344        Data['DetDepthRef'] = False
345        Data['calibrant'] = ''
346        Data['IOtth'] = [5.0,50.0]
347        Data['LRazimuth'] = [0.,180.]
348        Data['azmthOff'] = 0.0
349        Data['outChannels'] = 2500
350        Data['outAzimuths'] = 1
351        Data['centerAzm'] = False
352        Data['fullIntegrate'] = GSASIIpath.GetConfigValue('fullIntegrate',True)
353        Data['setRings'] = False
354        Data['background image'] = ['',-1.0]                           
355        Data['dark image'] = ['',-1.0]
356        Data['Flat Bkg'] = 0.0
357        Data['Oblique'] = [0.5,False]
358    Data['setDefault'] = False
359    Data['range'] = [(0,Imax),[0,Imax]]
360    G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Image Controls'),Data)
361    Masks = {'Points':[],'Rings':[],'Arcs':[],'Polygons':[],'Frames':[],'Thresholds':[(0,Imax),[0,Imax]]}
362    G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Masks'),Masks)
363    G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Stress/Strain'),
364        {'Type':'True','d-zero':[],'Sample phi':0.0,'Sample z':0.0,'Sample load':0.0})
365    G2frame.GPXtree.SetItemPyData(Id,[Npix,imageInfo])
366    G2frame.PickId = Id
367    G2frame.PickIdText = G2frame.GetTreeItemsList(G2frame.PickId)
368    G2frame.Image = Id
369
370def GetImageData(G2frame,imagefile,imageOnly=False,ImageTag=None,FormatName=''):
371    '''Read a single image with an image importer. This is called to reread an image
372    after it has already been imported with :meth:`GSASIIdataGUI.GSASII.OnImportGeneric`
373    (or :func:`ReadImages` in Auto Integration) so it is not necessary to reload metadata.
374
375    :param wx.Frame G2frame: main GSAS-II Frame and data object.
376    :param str imagefile: name of image file
377    :param bool imageOnly: If True return only the image,
378      otherwise  (default) return more (see below)
379    :param int/str ImageTag: specifies a particular image to be read from a file.
380      First image is read if None (default).
381    :param str formatName: the image reader formatName
382
383    :returns: an image as a numpy array or a list of four items:
384      Comments, Data, Npix and the Image, as selected by imageOnly
385
386    '''
387    # determine which formats are compatible with this file
388    primaryReaders = []
389    secondaryReaders = []
390    for rd in G2frame.ImportImageReaderlist:
391        flag = rd.ExtensionValidator(imagefile)
392        if flag is None: 
393            secondaryReaders.append(rd)
394        elif flag:
395            if not FormatName:
396                primaryReaders.append(rd)
397            elif FormatName == rd.formatName:
398                primaryReaders.append(rd)
399    if len(secondaryReaders) + len(primaryReaders) == 0:
400        print('Error: No matching format for file '+imagefile)
401        raise Exception('No image read')
402    errorReport = ''
403    if not imagefile:
404        return
405    for rd in primaryReaders+secondaryReaders:
406        rd.ReInitialize() # purge anything from a previous read
407        rd.errors = "" # clear out any old errors
408        if not rd.ContentsValidator(imagefile): # rejected on cursory check
409            errorReport += "\n  "+rd.formatName + ' validator error'
410            if rd.errors: 
411                errorReport += ': '+rd.errors
412                continue
413        if imageOnly:
414            ParentFrame = None # prevent GUI access on reread
415        else:
416            ParentFrame = G2frame
417        if GSASIIpath.GetConfigValue('debug'):
418            flag = rd.Reader(imagefile,ParentFrame,blocknum=ImageTag)
419        else:
420            flag = False
421            try:
422                flag = rd.Reader(imagefile,ParentFrame,blocknum=ImageTag)
423            except rd.ImportException as detail:
424                rd.errors += "\n  Read exception: "+str(detail)
425            except Exception as detail:
426                import traceback
427                rd.errors += "\n  Unhandled read exception: "+str(detail)
428                rd.errors += "\n  Traceback info:\n"+str(traceback.format_exc())
429        if flag: # this read succeeded
430            if rd.Image is None:
431                raise Exception('No image read. Strange!')
432            if GSASIIpath.GetConfigValue('Transpose'):
433                print ('Transposing Image!')
434                rd.Image = rd.Image.T
435            #rd.readfilename = imagefile
436            if imageOnly:
437                return rd.Image
438            else:
439                return rd.Comments,rd.Data,rd.Npix,rd.Image
440    else:
441        print('Error reading file '+imagefile)
442        print('Error messages(s)\n'+errorReport)
443        raise Exception('No image read')   
444
445def ReadImages(G2frame,imagefile):
446    '''Read one or more images from a file and put them into the Tree
447    using image importers. Called only in :meth:`AutoIntFrame.OnTimerLoop`.
448
449    ToDo: Images are most commonly read in :meth:`GSASIIdataGUI.GSASII.OnImportGeneric`
450    which is called from :meth:`GSASIIdataGUI.GSASII.OnImportImage`
451    it would be good if these routines used a common code core so that changes need to
452    be made in only one place.
453
454    :param wx.Frame G2frame: main GSAS-II Frame and data object.
455    :param str imagefile: name of image file
456
457    :returns: a list of the id's of the IMG tree items created
458    '''
459    # determine which formats are compatible with this file
460    primaryReaders = []
461    secondaryReaders = []
462    for rd in G2frame.ImportImageReaderlist:
463        flag = rd.ExtensionValidator(imagefile)
464        if flag is None:
465            secondaryReaders.append(rd)
466        elif flag:
467            primaryReaders.append(rd)
468    if len(secondaryReaders) + len(primaryReaders) == 0:
469        print('Error: No matching format for file '+imagefile)
470        raise Exception('No image read')
471    errorReport = ''
472    rdbuffer = {} # create temporary storage for file reader
473    for rd in primaryReaders+secondaryReaders:
474        rd.ReInitialize() # purge anything from a previous read
475        rd.errors = "" # clear out any old errors
476        if not rd.ContentsValidator(imagefile): # rejected on cursory check
477            errorReport += "\n  "+rd.formatName + ' validator error'
478            if rd.errors: 
479                errorReport += ': '+rd.errors
480                continue
481        ParentFrame = G2frame
482        block = 0
483        repeat = True
484        CreatedIMGitems = []
485        while repeat: # loop if the reader asks for another pass on the file
486            block += 1
487            repeat = False
488            if GSASIIpath.GetConfigValue('debug'):
489                flag = rd.Reader(imagefile,ParentFrame,blocknum=block,Buffer=rdbuffer)
490            else:
491                flag = False
492                try:
493                    flag = rd.Reader(imagefile,ParentFrame,blocknum=block,Buffer=rdbuffer)
494                except rd.ImportException as detail:
495                    rd.errors += "\n  Read exception: "+str(detail)
496                except Exception as detail:
497                    import traceback
498                    rd.errors += "\n  Unhandled read exception: "+str(detail)
499                    rd.errors += "\n  Traceback info:\n"+str(traceback.format_exc())
500            if flag: # this read succeeded
501                if rd.Image is None:
502                    raise Exception('No image read. Strange!')
503                if GSASIIpath.GetConfigValue('Transpose'):
504                    print ('Transposing Image!')
505                    rd.Image = rd.Image.T
506                rd.Data['ImageTag'] = rd.repeatcount
507                rd.readfilename = imagefile
508                # Load generic metadata, as configured
509                G2fil.GetColumnMetadata(rd)
510                LoadImage2Tree(imagefile,G2frame,rd.Comments,rd.Data,rd.Npix,rd.Image)
511                repeat = rd.repeat
512            CreatedIMGitems.append(G2frame.Image)
513        if CreatedIMGitems: return CreatedIMGitems
514    else:
515        print('Error reading file '+imagefile)
516        print('Error messages(s)\n'+errorReport)
517        return []
518        #raise Exception('No image read')   
519
520def SaveMultipleImg(G2frame):
521    if not G2frame.GPXtree.GetCount():
522        print ('no images!')
523        return
524    choices = G2gd.GetGPXtreeDataNames(G2frame,['IMG ',])
525    if len(choices) == 1:
526        names = choices
527    else:
528        dlg = G2G.G2MultiChoiceDialog(G2frame,'Stress/Strain fitting','Select images to fit:',choices)
529        dlg.SetSelections([])
530        names = []
531        if dlg.ShowModal() == wx.ID_OK:
532            names = [choices[sel] for sel in dlg.GetSelections()]
533        dlg.Destroy()
534    if not names: return
535    for name in names:
536        Id = G2gd.GetGPXtreeItemId(G2frame, G2frame.root, name)
537        Npix,imagefile,imagetag = G2frame.GPXtree.GetImageLoc(Id)
538        imroot = os.path.splitext(imagefile)[0]
539        if imagetag:
540            imroot += '_' + str(imagetag)
541        Data = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id, 'Image Controls'))
542        print('Writing '+imroot+'.imctrl')
543        File = open(imroot+'.imctrl','w')
544        keys = ['type','wavelength','calibrant','distance','center',
545                    'tilt','rotation','azmthOff','fullIntegrate','LRazimuth',
546                    'IOtth','outChannels','outAzimuths','invert_x','invert_y','DetDepth',
547                    'calibskip','pixLimit','cutoff','calibdmin','chisq','Flat Bkg',
548                    'binType','SampleShape','PolaVal','SampleAbs','dark image','background image']
549        for key in keys:
550            if key not in Data: continue    #uncalibrated!
551            File.write(key+':'+str(Data[key])+'\n')
552        File.close()
553        mask = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id, 'Masks'))
554        G2imG.CleanupMasks(mask)
555        print('Writing '+imroot+'.immask')
556        File = open(imroot+'.immask','w')
557        for key in ['Points','Rings','Arcs','Polygons','Frames','Thresholds']:
558            File.write(key+':'+str(mask[key])+'\n')
559        File.close()
560       
561def PutG2Image(filename,Comments,Data,Npix,image):
562    'Write an image as a python pickle - might be better as an .edf file?'
563    File = open(filename,'wb')
564    cPickle.dump([Comments,Data,Npix,image],File,2)
565    File.close()
566    return
567
568objectScanIgnore = [int,bool,float,str,np.float64,np.float32,np.int32,np.int64,np.ndarray,G2obj.G2VarObj,G2obj.ExpressionObj]
569try:
570    objectScanIgnore += [ma.MaskedArray] # fails in doc builds
571except AttributeError:
572    pass
573   
574if '2' in platform.python_version_tuple()[0]:
575    objectScanIgnore += [unicode,long,]
576   
577def objectScan(data,tag,indexStack=[]):
578    '''Recursively scan an object looking for unexpected data types.
579    This is used in debug mode to scan .gpx files for objects we did not
580    intend to be there.
581    '''
582    if type(data) is list or type(data) is tuple:
583        for i in range(len(data)):
584            val = objectScan(data[i],tag,indexStack+[i])
585            if val:
586                data[i] = val
587                print('...fixed')
588    elif type(data) is dict:
589        for key in data:
590            val = objectScan(data[key],tag,indexStack+[key])
591            if val:
592                data[key] = val
593                print('...fixed')
594    elif data is None:
595        return None
596    elif type(data) in objectScanIgnore:
597        return None
598    else:
599        s = 'unexpected object in '+tag
600        for i in indexStack:
601            s += "[{}]".format(i)
602        #print(s,data.__class__.__name__) # loses full name of class
603        print(s,type(data))
604        global unexpectedObject
605        unexpectedObject = True
606        # fix bad objects
607        if "gdi.Colour" in str(type(data)):
608            return tuple(data)
609        return
610   
611def cPickleLoad(fp):
612    if '2' in platform.python_version_tuple()[0]:
613        return cPickle.load(fp)
614    else:
615       return cPickle.load(fp,encoding='latin-1')
616
617def ProjFileOpen(G2frame,showProvenance=True):
618    'Read a GSAS-II project file and load into the G2 data tree'
619    if not os.path.exists(G2frame.GSASprojectfile):
620        print ('\n*** Error attempt to open project file that does not exist:\n   '+
621               str(G2frame.GSASprojectfile))
622        return
623    LastSavedUsing = None
624    filep = open(G2frame.GSASprojectfile,'rb')
625    if showProvenance: print ('loading from file: '+G2frame.GSASprojectfile)
626    GPXphase = os.path.splitext(G2frame.GSASprojectfile)[0]+'.seqPhase'
627    GPXhist = os.path.splitext(G2frame.GSASprojectfile)[0]+'.seqHist'
628    deleteSeq = False
629    hist = None
630    tmpHistIndex = {}
631    updateFromSeq = False
632    if os.path.exists(GPXphase) and os.path.exists(GPXhist):
633        dlg = wx.MessageDialog(G2frame,
634            'Load results from crashed sequential fit?\nNo deletes the files!', 'Recover partial sequential fit?', wx.YES | wx.NO | wx.CANCEL)
635        dlg.CenterOnParent()
636        try:
637            result = dlg.ShowModal()
638            deleteSeq = result != wx.ID_CANCEL
639            if result == wx.ID_YES:
640                updateFromSeq = True
641                fp = open(GPXphase,'rb')
642                data = cPickleLoad(fp) # first block in file should be Phases
643                if data[0][0] != 'Phases':
644                    raise Exception('Unexpected block in {} file. How did this happen?'
645                            .format(GPXphase))
646                Phases = {}
647                for name,vals in data[1:]:
648                    Phases[name] = vals
649                name,CovData = cPickleLoad(fp)[0] # 2nd block in file should be Covariance
650                name,RigidBodies = cPickleLoad(fp)[0] # 3rd block in file should be Rigid Bodies
651                fp.close()
652                # index the histogram updates
653                hist = open(GPXhist,'rb')
654                try:
655                    while True:
656                        loc = hist.tell()
657                        datum = cPickleLoad(hist)[0]
658                        tmpHistIndex[datum[0]] = loc
659                except EOFError:
660                    pass
661        finally:
662            dlg.Destroy()
663    wx.BeginBusyCursor()
664    try:
665        while True:
666            try:
667                data = cPickleLoad(filep)
668            except EOFError:
669                break
670            datum = data[0]
671            # scan the GPX file for unexpected objects
672            if GSASIIpath.GetConfigValue('debug'):
673                global unexpectedObject
674                unexpectedObject = False               
675                objectScan(data,'tree item "{}" entry '.format(datum[0]))
676                #if unexpectedObject:
677                #    print(datum[0])
678                #    GSASIIpath.IPyBreak()
679            Id = G2frame.GPXtree.AppendItem(parent=G2frame.root,text=datum[0])
680            if updateFromSeq and datum[0] == 'Phases':
681                for pdata in data[1:]:
682                    if pdata[0] in Phases:
683                        pdata[1].update(Phases[pdata[0]])
684            elif updateFromSeq and datum[0] == 'Covariance':
685                data[0][1] = CovData
686            elif updateFromSeq and datum[0] == 'Rigid bodies':
687                data[0][1] = RigidBodies
688            elif updateFromSeq and datum[0] in tmpHistIndex:
689                hist.seek(tmpHistIndex[datum[0]])
690                hdata = cPickleLoad(hist)
691                if data[0][0] != hdata[0][0]:
692                    print('Error! Updating {} with {}'.format(data[0][0],hdata[0][0]))
693                datum = hdata[0]
694                xferItems = ['Background','Instrument Parameters','Sample Parameters','Reflection Lists']
695                hItems = {name:j+1 for j,(name,val) in enumerate(hdata[1:]) if name in xferItems}
696                for j,(name,val) in enumerate(data[1:]):
697                    if name not in xferItems: continue
698                    data[j+1][1] = hdata[hItems[name]][1]
699            if datum[0].startswith('PWDR'):               
700                if 'ranId' not in datum[1][0]: # patch: add random Id if not present
701                    datum[1][0]['ranId'] = ran.randint(0,sys.maxsize)
702                G2frame.GPXtree.SetItemPyData(Id,datum[1][:3])  #temp. trim off junk (patch?)
703            elif datum[0].startswith('HKLF'): 
704                if 'ranId' not in datum[1][0]: # patch: add random Id if not present
705                    datum[1][0]['ranId'] = ran.randint(0,sys.maxsize)
706                G2frame.GPXtree.SetItemPyData(Id,datum[1])
707            else:
708                G2frame.GPXtree.SetItemPyData(Id,datum[1])             
709                if datum[0] == 'Controls' and 'LastSavedUsing' in datum[1]:
710                    LastSavedUsing = datum[1]['LastSavedUsing']
711                if datum[0] == 'Controls' and 'PythonVersions' in datum[1] and GSASIIpath.GetConfigValue('debug') and showProvenance:
712                    print('DBG_Packages used to create .GPX file:')
713                    if 'dict' in str(type(datum[1]['PythonVersions'])):  #patch
714                        for p in sorted(datum[1]['PythonVersions'],key=lambda s: s.lower()):
715                            print({:<14s}: {:s}".format(p[0],p[1]))
716                    else:
717                        for p in datum[1]['PythonVersions']:
718                            print({:<12s} {:s}".format(p[0]+':',p[1]))
719            oldPDF = False
720            for datus in data[1:]:
721#patch - 1/23/17 PDF cleanup
722                if datus[0][:4] in ['I(Q)','S(Q)','F(Q)','G(R)']:
723                    oldPDF = True
724                    data[1][1][datus[0][:4]] = copy.deepcopy(datus[1][:2])
725                    continue
726#end PDF cleanup
727                sub = G2frame.GPXtree.AppendItem(Id,datus[0])
728#patch
729                if datus[0] == 'Instrument Parameters' and len(datus[1]) == 1:
730                    if datum[0].startswith('PWDR'):
731                        datus[1] = [dict(zip(datus[1][3],zip(datus[1][0],datus[1][1],datus[1][2]))),{}]
732                    else:
733                        datus[1] = [dict(zip(datus[1][2],zip(datus[1][0],datus[1][1]))),{}]
734                    for item in datus[1][0]:               #zip makes tuples - now make lists!
735                        datus[1][0][item] = list(datus[1][0][item])
736#end patch
737                G2frame.GPXtree.SetItemPyData(sub,datus[1])
738            if 'PDF ' in datum[0][:4] and oldPDF:
739                sub = G2frame.GPXtree.AppendItem(Id,'PDF Peaks')
740                G2frame.GPXtree.SetItemPyData(sub,{'Limits':[1.,5.],'Background':[2,[0.,-0.2*np.pi],False],'Peaks':[]})
741            if datum [0].startswith('IMG'):                   #retrieve image default flag & data if set
742                Data = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id,'Image Controls'))
743                if Data['setDefault']:
744                    G2frame.imageDefault = Data               
745        if LastSavedUsing:
746            print('GPX load successful. Last saved with GSAS-II revision '+LastSavedUsing)
747        else:
748            print('project load successful')
749        G2frame.NewPlot = True
750    except Exception as errmsg:
751        if GSASIIpath.GetConfigValue('debug'):
752            print('\nError reading GPX file:',errmsg)
753            import traceback
754            print (traceback.format_exc())
755        msg = wx.MessageDialog(G2frame,message="Error reading file "+
756            str(G2frame.GSASprojectfile)+". This is not a current GSAS-II .gpx file",
757            caption="Load Error",style=wx.ICON_ERROR | wx.OK | wx.STAY_ON_TOP)
758        msg.ShowModal()
759    finally:
760        filep.close()
761        wx.EndBusyCursor()
762        G2frame.Status.SetStatusText('Mouse RB drag/drop to reorder',0)
763    if deleteSeq:
764        if hist: hist.close()
765        try:
766            os.remove(GPXphase)
767        except:
768            print('Warning: unable to delete {}'.format(GPXphase))
769        try:
770            os.remove(GPXhist)
771        except:
772            print('Warning: unable to delete {}'.format(GPXhist))
773    G2frame.SetTitleByGPX()
774   
775def ProjFileSave(G2frame):
776    'Save a GSAS-II project file'
777    if not G2frame.GPXtree.IsEmpty():
778        file = open(G2frame.GSASprojectfile,'wb')
779        print ('save to file: '+G2frame.GSASprojectfile)
780        # stick the file name into the tree and version info into tree so they are saved.
781        # (Controls should always be created at this point)
782        try:
783            Controls = G2frame.GPXtree.GetItemPyData(
784                G2gd.GetGPXtreeItemId(G2frame,G2frame.root, 'Controls'))
785            Controls['LastSavedAs'] = os.path.abspath(G2frame.GSASprojectfile)
786            Controls['LastSavedUsing'] = str(GSASIIpath.GetVersionNumber())
787            Controls['PythonVersions'] = G2frame.PackageVersions
788        except:
789            pass
790        wx.BeginBusyCursor()
791        try:
792            item, cookie = G2frame.GPXtree.GetFirstChild(G2frame.root)
793            while item:
794                data = []
795                name = G2frame.GPXtree.GetItemText(item)
796                data.append([name,G2frame.GPXtree.GetItemPyData(item)])
797                item2, cookie2 = G2frame.GPXtree.GetFirstChild(item)
798                while item2:
799                    name = G2frame.GPXtree.GetItemText(item2)
800                    data.append([name,G2frame.GPXtree.GetItemPyData(item2)])
801                    item2, cookie2 = G2frame.GPXtree.GetNextChild(item, cookie2)                           
802                item, cookie = G2frame.GPXtree.GetNextChild(G2frame.root, cookie)                           
803                cPickle.dump(data,file,2)
804            file.close()
805            pth = os.path.split(os.path.abspath(G2frame.GSASprojectfile))[0]
806            if GSASIIpath.GetConfigValue('Save_paths'): G2G.SaveGPXdirectory(pth)
807            G2frame.LastGPXdir = pth
808        finally:
809            wx.EndBusyCursor()
810        print('project save successful')
811
812def SaveIntegration(G2frame,PickId,data,Overwrite=False):
813    'Save image integration results as powder pattern(s)'
814    waves = {'Cu':[1.54051,1.54433],'Ti':[2.74841,2.75207],'Cr':[2.28962,2.29351],
815        'Fe':[1.93597,1.93991],'Co':[1.78892,1.79278],'Mo':[0.70926,0.713543],
816        'Ag':[0.559363,0.563775]}
817    azms = G2frame.Integrate[1]
818    X = G2frame.Integrate[2][:-1]
819    N = len(X)
820    Id = G2frame.GPXtree.GetItemParent(PickId)
821    name = G2frame.GPXtree.GetItemText(Id)
822    name = name.replace('IMG ',data['type']+' ')
823    Comments = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id, 'Comments'))
824    Controls = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,G2frame.root, 'Controls'))
825    if 'PWDR' in name:
826        if 'target' in data:
827            names = ['Type','Lam1','Lam2','I(L2)/I(L1)','Zero','Polariz.','U','V','W','X','Y','Z','SH/L','Azimuth'] 
828            codes = [0 for i in range(14)]
829        else:
830            names = ['Type','Lam','Zero','Polariz.','U','V','W','X','Y','Z','SH/L','Azimuth'] 
831            codes = [0 for i in range(12)]
832    elif 'SASD' in name:
833        names = ['Type','Lam','Zero','Azimuth'] 
834        codes = [0 for i in range(4)]
835        X = 4.*np.pi*npsind(X/2.)/data['wavelength']    #convert to q
836    Xminmax = [X[0],X[-1]]
837    Azms = np.zeros(data['outAzimuths'])
838    dazm = 0.
839#    if data['fullIntegrate'] and data['outAzimuths'] == 1:
840#        Azms = [0.0,]                              #a poor man's average?
841#    else:
842##        for i,azm in enumerate(azms[:-1]):
843##            if azm > 360. and azms[i+1] > 360.:
844##                Azms.append(G2img.meanAzm(azm%360.,azms[i+1]%360.))
845##            else:   
846##                Azms.append(G2img.meanAzm(azm,azms[i+1]))
847    if data['outAzimuths'] > 1:
848        dazm = np.min(np.abs(np.diff(azms)))/2.
849    G2frame.IntgOutList = []
850    for i,azm in enumerate(azms[:-1]):
851        Aname = name+" Azm= %.2f"%((azm+dazm)%360.)
852        item, cookie = G2frame.GPXtree.GetFirstChild(G2frame.root)
853        # if Overwrite delete any duplicate
854        if Overwrite and G2gd.GetGPXtreeItemId(G2frame,G2frame.root,Aname):
855            print('Replacing '+Aname)
856            item = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,Aname)
857            G2frame.GPXtree.Delete(item)
858        else:
859            nOcc = 0
860            while item:
861                Name = G2frame.GPXtree.GetItemText(item)
862                if Aname in Name:
863                    nOcc += 1
864                item, cookie = G2frame.GPXtree.GetNextChild(G2frame.root, cookie)
865            if nOcc:
866                Aname += '(%d)'%(nOcc)
867        Sample = G2obj.SetDefaultSample()       #set as Debye-Scherrer
868        Sample['Gonio. radius'] = data['distance']
869        Sample['Omega'] = data['GonioAngles'][0]
870        Sample['Chi'] = data['GonioAngles'][1]
871        Sample['Phi'] = data['GonioAngles'][2]
872        Sample['Azimuth'] = (azm+dazm)%360.    #put here as bin center
873        polariz = 0.99    #set default polarization for synchrotron radiation!
874        for item in Comments:
875            if 'polariz' in item:
876                try:
877                    polariz = float(item.split('=')[1])
878                except:
879                    polariz = 0.99
880            for key in ('Temperature','Pressure','Time','FreePrm1','FreePrm2','FreePrm3','Omega',
881                'Chi','Phi'):
882                if key.lower() in item.lower():
883                    try:
884                        Sample[key] = float(item.split('=')[1])
885                    except:
886                        pass
887            if 'label_prm' in item.lower():
888                for num in ('1','2','3'):
889                    if 'label_prm'+num in item.lower():
890                        Controls['FreePrm'+num] = item.split('=')[1].strip()
891        if 'PWDR' in Aname:
892            if 'target' in data:    #from lab x-ray 2D imaging data
893                wave1,wave2 = waves[data['target']]
894                parms = ['PXC',wave1,wave2,0.5,0.0,polariz,290.,-40.,30.,6.,-14.,0.0,0.0001,Azms[i]]
895            else:
896                parms = ['PXC',data['wavelength'],0.0,polariz,1.0,-0.10,0.4,0.30,1.0,0.0,0.0001,Azms[i]]
897        elif 'SASD' in Aname:
898            Sample['Trans'] = data['SampleAbs'][0]
899            parms = ['LXC',data['wavelength'],0.0,Azms[i]]
900        Y = G2frame.Integrate[0][i]
901        Ymin = np.min(Y)
902        Ymax = np.max(Y)
903        W = np.where(Y>0.,1./Y,1.e-6)                    #probably not true
904        Id = G2frame.GPXtree.AppendItem(parent=G2frame.root,text=Aname)
905        G2frame.IntgOutList.append(Id)
906        G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Comments'),Comments)                   
907        G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Limits'),copy.deepcopy([tuple(Xminmax),Xminmax]))
908        if 'PWDR' in Aname:
909            G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Background'),[['chebyschev',1,3,1.0,0.0,0.0],
910                {'nDebye':0,'debyeTerms':[],'nPeaks':0,'peaksList':[]}])
911        inst = [dict(zip(names,zip(parms,parms,codes))),{}]
912        for item in inst[0]:
913            inst[0][item] = list(inst[0][item])
914        G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Instrument Parameters'),inst)
915        if 'PWDR' in Aname:
916            G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Sample Parameters'),Sample)
917            G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Peak List'),{'sigDict':{},'peaks':[]})
918            G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Index Peak List'),[[],[]])
919            G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Unit Cells List'),[])
920            G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Reflection Lists'),{})
921        elif 'SASD' in Aname:             
922            G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Substances'),G2pdG.SetDefaultSubstances())
923            G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Sample Parameters'),Sample)
924            G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Models'),G2pdG.SetDefaultSASDModel())
925        valuesdict = {
926            'wtFactor':1.0,'Dummy':False,'ranId':ran.randint(0,sys.maxsize),'Offset':[0.0,0.0],'delOffset':0.02*Ymax,
927            'refOffset':-0.1*Ymax,'refDelt':0.1*Ymax,'Yminmax':[Ymin,Ymax]}
928        G2frame.GPXtree.SetItemPyData(Id,[valuesdict,
929            [np.array(X),np.array(Y),np.array(W),np.zeros(N),np.zeros(N),np.zeros(N)]])
930    return Id       #last powder pattern generated
931   
932def XYsave(G2frame,XY,labelX='X',labelY='Y',names=[]):
933    'Save XY table data'
934    pth = G2G.GetExportPath(G2frame)
935    dlg = wx.FileDialog(
936        G2frame, 'Enter csv filename for XY table', pth, '',
937        'XY table file (*.csv)|*.csv',wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)
938    try:
939        if dlg.ShowModal() == wx.ID_OK:
940            filename = dlg.GetPath()
941            filename = os.path.splitext(filename)[0]+'.csv'
942            File = open(filename,'w')
943        else:
944            filename = None
945    finally:
946        dlg.Destroy()
947    if not filename:
948        return
949    for i in range(len(XY)):
950        if len(names):
951            header = '%s,%s(%s)\n'%(labelX,labelY,names[i])
952        else:
953            header = '%s,%s(%d)\n'%(labelX,labelY,i)
954        File.write(header)
955        for x,y in XY[i].T:
956            File.write('%.3f,%.3f\n'%(x,y))   
957    File.close()
958    print (' XY data saved to: '+filename)                       
959   
960def PeakListSave(G2frame,file,peaks):
961    'Save powder peaks to a data file'
962    print ('save peak list to file: '+G2frame.peaklistfile)
963    if not peaks:
964        dlg = wx.MessageDialog(G2frame, 'No peaks!', 'Nothing to save!', wx.OK)
965        try:
966            dlg.ShowModal()
967        finally:
968            dlg.Destroy()
969        return
970    for peak in peaks:
971        file.write("%10.4f %12.2f %10.3f %10.3f \n" % \
972            (peak[0],peak[2],peak[4],peak[6]))
973    print ('peak list saved')
974             
975def IndexPeakListSave(G2frame,peaks):
976    'Save powder peaks from the indexing list'
977    file = open(G2frame.peaklistfile,'wa')
978    print ('save index peak list to file: '+G2frame.peaklistfile)
979    wx.BeginBusyCursor()
980    try:
981        if not peaks:
982            dlg = wx.MessageDialog(G2frame, 'No peaks!', 'Nothing to save!', wx.OK)
983            try:
984                dlg.ShowModal()
985            finally:
986                dlg.Destroy()
987            return
988        for peak in peaks:
989            file.write("%12.6f\n" % (peak[7]))
990        file.close()
991    finally:
992        wx.EndBusyCursor()
993    print ('index peak list saved')
994   
995class MultipleChoicesDialog(wx.Dialog):
996    '''A dialog that offers a series of choices, each with a
997    title and a wx.Choice widget. Intended to be used Modally.
998    typical input:
999
1000        *  choicelist=[ ('a','b','c'), ('test1','test2'),('no choice',)]
1001        *  headinglist = [ 'select a, b or c', 'select 1 of 2', 'No option here']
1002       
1003    selections are placed in self.chosen when OK is pressed
1004
1005    Also see GSASIIctrlGUI
1006    '''
1007    def __init__(self,choicelist,headinglist,
1008                 head='Select options',
1009                 title='Please select from options below',
1010                 parent=None):
1011        self.chosen = []
1012        wx.Dialog.__init__(
1013            self,parent,wx.ID_ANY,head, 
1014            pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE)
1015        panel = wx.Panel(self)
1016        mainSizer = wx.BoxSizer(wx.VERTICAL)
1017        mainSizer.Add((10,10),1)
1018        topLabl = wx.StaticText(panel,wx.ID_ANY,title)
1019        mainSizer.Add(topLabl,0,wx.ALIGN_CENTER_VERTICAL|wx.CENTER,10)
1020        self.ChItems = []
1021        for choice,lbl in zip(choicelist,headinglist):
1022            mainSizer.Add((10,10),1)
1023            self.chosen.append(0)
1024            topLabl = wx.StaticText(panel,wx.ID_ANY,' '+lbl)
1025            mainSizer.Add(topLabl,0,wx.ALIGN_LEFT,10)
1026            self.ChItems.append(wx.Choice(self, wx.ID_ANY, (100, 50), choices = choice))
1027            mainSizer.Add(self.ChItems[-1],0,wx.ALIGN_CENTER,10)
1028
1029        OkBtn = wx.Button(panel,-1,"Ok")
1030        OkBtn.Bind(wx.EVT_BUTTON, self.OnOk)
1031        cancelBtn = wx.Button(panel,-1,"Cancel")
1032        cancelBtn.Bind(wx.EVT_BUTTON, self.OnCancel)
1033        btnSizer = wx.BoxSizer(wx.HORIZONTAL)
1034        btnSizer.Add((20,20),1)
1035        btnSizer.Add(OkBtn)
1036        btnSizer.Add((20,20),1)
1037        btnSizer.Add(cancelBtn)
1038        btnSizer.Add((20,20),1)
1039        mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP, 10)
1040        panel.SetSizer(mainSizer)
1041        panel.Fit()
1042        self.Fit()
1043       
1044    def OnOk(self,event):
1045        parent = self.GetParent()
1046        if parent is not None: parent.Raise()
1047        # save the results from the choice widgets
1048        self.chosen = []
1049        for w in self.ChItems:
1050            self.chosen.append(w.GetSelection())
1051        self.EndModal(wx.ID_OK)             
1052           
1053    def OnCancel(self,event):
1054        parent = self.GetParent()
1055        if parent is not None: parent.Raise()
1056        self.chosen = []
1057        self.EndModal(wx.ID_CANCEL)             
1058           
1059def ExtractFileFromZip(filename, selection=None, confirmread=True,
1060                       confirmoverwrite=True, parent=None,
1061                       multipleselect=False):
1062    '''If the filename is a zip file, extract a file from that
1063    archive.
1064
1065    :param list Selection: used to predefine the name of the file
1066      to be extracted. Filename case and zip directory name are
1067      ignored in selection; the first matching file is used.
1068
1069    :param bool confirmread: if True asks the user to confirm before expanding
1070      the only file in a zip
1071
1072    :param bool confirmoverwrite: if True asks the user to confirm
1073      before overwriting if the extracted file already exists
1074
1075    :param bool multipleselect: if True allows more than one zip
1076      file to be extracted, a list of file(s) is returned.
1077      If only one file is present, do not ask which one, otherwise
1078      offer a list of choices (unless selection is used).
1079   
1080    :returns: the name of the file that has been created or a
1081      list of files (see multipleselect)
1082
1083    If the file is not a zipfile, return the name of the input file.
1084    If the zipfile is empty or no file has been selected, return None
1085    '''
1086    import zipfile # do this now, since we can save startup time by doing this only on need
1087    import shutil
1088    zloc = os.path.split(filename)[0]
1089    if not zipfile.is_zipfile(filename):
1090        #print("not zip")
1091        return filename
1092
1093    z = zipfile.ZipFile(filename,'r')
1094    zinfo = z.infolist()
1095
1096    if len(zinfo) == 0:
1097        #print('Zip has no files!')
1098        zlist = [-1]
1099    if selection:
1100        choices = [os.path.split(i.filename)[1].lower() for i in zinfo]
1101        if selection.lower() in choices:
1102            zlist = [choices.index(selection.lower())]
1103        else:
1104            print('debug: file '+str(selection)+' was not found in '+str(filename))
1105            zlist = [-1]
1106    elif len(zinfo) == 1 and confirmread:
1107        result = wx.ID_NO
1108        dlg = wx.MessageDialog(
1109            parent,
1110            'Is file '+str(zinfo[0].filename)+
1111            ' what you want to extract from '+
1112            str(os.path.split(filename)[1])+'?',
1113            'Confirm file', 
1114            wx.YES_NO | wx.ICON_QUESTION)
1115        try:
1116            result = dlg.ShowModal()
1117        finally:
1118            dlg.Destroy()
1119        if result == wx.ID_NO:
1120            zlist = [-1]
1121        else:
1122            zlist = [0]
1123    elif len(zinfo) == 1:
1124        zlist = [0]
1125    elif multipleselect:
1126        # select one or more from a from list
1127        choices = [i.filename for i in zinfo]
1128        dlg = G2G.G2MultiChoiceDialog(parent,'Select file(s) to extract from zip file '+str(filename),
1129            'Choose file(s)',choices)
1130        if dlg.ShowModal() == wx.ID_OK:
1131            zlist = dlg.GetSelections()
1132        else:
1133            zlist = []
1134        dlg.Destroy()
1135    else:
1136        # select one from a from list
1137        choices = [i.filename for i in zinfo]
1138        dlg = wx.SingleChoiceDialog(parent,
1139            'Select file to extract from zip file'+str(filename),'Choose file',
1140            choices,)
1141        if dlg.ShowModal() == wx.ID_OK:
1142            zlist = [dlg.GetSelection()]
1143        else:
1144            zlist = [-1]
1145        dlg.Destroy()
1146       
1147    outlist = []
1148    for zindex in zlist:
1149        if zindex >= 0:
1150            efil = os.path.join(zloc, os.path.split(zinfo[zindex].filename)[1])
1151            if os.path.exists(efil) and confirmoverwrite:
1152                result = wx.ID_NO
1153                dlg = wx.MessageDialog(parent,
1154                    'File '+str(efil)+' already exists. OK to overwrite it?',
1155                    'Confirm overwrite',wx.YES_NO | wx.ICON_QUESTION)
1156                try:
1157                    result = dlg.ShowModal()
1158                finally:
1159                    dlg.Destroy()
1160                if result == wx.ID_NO:
1161                    zindex = -1
1162        if zindex >= 0:
1163            # extract the file to the current directory, regardless of it's original path
1164            #z.extract(zinfo[zindex],zloc)
1165            eloc,efil = os.path.split(zinfo[zindex].filename)
1166            outfile = os.path.join(zloc, efil)
1167            fpin = z.open(zinfo[zindex])
1168            fpout = open(outfile, "wb")
1169            shutil.copyfileobj(fpin, fpout)
1170            fpin.close()
1171            fpout.close()
1172            outlist.append(outfile)
1173    z.close()
1174    if multipleselect and len(outlist) >= 1:
1175        return outlist
1176    elif len(outlist) == 1:
1177        return outlist[0]
1178    else:
1179        return None
1180
1181######################################################################
1182# base classes for reading various types of data files
1183#   not used directly, only by subclassing
1184######################################################################
1185def BlockSelector(ChoiceList, ParentFrame=None,title='Select a block',
1186    size=None, header='Block Selector',useCancel=True):
1187    ''' Provide a wx dialog to select a block if the file contains more
1188    than one set of data and one must be selected
1189    '''
1190    if useCancel:
1191        dlg = wx.SingleChoiceDialog(
1192            ParentFrame,title, header,ChoiceList)
1193    else:
1194        dlg = wx.SingleChoiceDialog(
1195            ParentFrame,title, header,ChoiceList,
1196            style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER|wx.OK|wx.CENTRE)
1197    if size: dlg.SetSize(size)
1198    dlg.CenterOnParent()
1199    if dlg.ShowModal() == wx.ID_OK:
1200        sel = dlg.GetSelection()
1201        return sel
1202    else:
1203        return None
1204    dlg.Destroy()
1205
1206def MultipleBlockSelector(ChoiceList, ParentFrame=None,
1207    title='Select a block',size=None, header='Block Selector'):
1208    '''Provide a wx dialog to select a block of data if the
1209    file contains more than one set of data and one must be
1210    selected.
1211
1212    :returns: a list of the selected blocks
1213    '''
1214    dlg = wx.MultiChoiceDialog(ParentFrame,title, header,ChoiceList+['Select all'],
1215        wx.CHOICEDLG_STYLE)
1216    dlg.CenterOnScreen()
1217    if size: dlg.SetSize(size)
1218    if dlg.ShowModal() == wx.ID_OK:
1219        sel = dlg.GetSelections()
1220    else:
1221        return []
1222    dlg.Destroy()
1223    selected = []
1224    if len(ChoiceList) in sel:
1225        return range(len(ChoiceList))
1226    else:
1227        return sel
1228    return selected
1229
1230def MultipleChoicesSelector(choicelist, headinglist, ParentFrame=None, **kwargs):
1231    '''A modal dialog that offers a series of choices, each with a title and a wx.Choice
1232    widget. Typical input:
1233   
1234       * choicelist=[ ('a','b','c'), ('test1','test2'),('no choice',)]
1235       
1236       * headinglist = [ 'select a, b or c', 'select 1 of 2', 'No option here']
1237       
1238    optional keyword parameters are: head (window title) and title
1239    returns a list of selected indicies for each choice (or None)
1240    '''
1241    result = None
1242    dlg = MultipleChoicesDialog(choicelist,headinglist,
1243        parent=ParentFrame, **kwargs)         
1244    dlg.CenterOnParent()
1245    if dlg.ShowModal() == wx.ID_OK:
1246        result = dlg.chosen
1247    dlg.Destroy()
1248    return result
1249
1250def PhaseSelector(ChoiceList, ParentFrame=None,
1251    title='Select a phase', size=None,header='Phase Selector'):
1252    ''' Provide a wx dialog to select a phase if the file contains more
1253    than one phase
1254    '''
1255    return BlockSelector(ChoiceList,ParentFrame,title,
1256        size,header)
1257
1258######################################################################
1259def striphist(var,insChar=''):
1260    'strip a histogram number from a var name'
1261    sv = var.split(':')
1262    if len(sv) <= 1: return var
1263    if sv[1]:
1264        sv[1] = insChar
1265    return ':'.join(sv)
1266class ExportBaseclass(object):
1267    '''Defines a base class for the exporting of GSAS-II results.
1268
1269    This class is subclassed in the various exports/G2export_*.py files. Those files
1270    are imported in :meth:`GSASIIdataGUI.GSASII._init_Exports` which defines the
1271    appropriate menu items for each one and the .Exporter method is called
1272    directly from the menu item.
1273
1274    Routines may also define a .Writer method, which is used to write a single
1275    file without invoking any GUI objects.
1276    '''
1277    # TODO: review exporters producing exceptions where .Writer can't be used where G2frame is None (see CIF)
1278    # TODO: review conflicting uses of .Writer with mode (SeqRef) & elsewhere
1279    # TODO: move this class to G2fil
1280    def __init__(self,G2frame,formatName,extension,longFormatName=None,):
1281        self.G2frame = G2frame
1282        self.formatName = formatName # short string naming file type
1283        self.extension = extension
1284        if longFormatName: # longer string naming file type
1285            self.longFormatName = longFormatName
1286        else:
1287            self.longFormatName = formatName
1288        self.OverallParms = {}
1289        self.Phases = {}
1290        self.Histograms = {}
1291        self.powderDict = {}
1292        self.sasdDict = {}
1293        self.xtalDict = {}
1294        self.parmDict = {}
1295        self.sigDict = {}
1296        # updated in InitExport:
1297        self.currentExportType = None # type of export that has been requested
1298        # updated in ExportSelect (when used):
1299        self.phasenam = None # a list of selected phases
1300        self.histnam = None # a list of selected histograms
1301        self.filename = None # name of file to be written (single export) or template (multiple files)
1302        self.dirname = '' # name of directory where file(s) will be written
1303        self.fullpath = '' # name of file being written -- full path
1304       
1305        # items that should be defined in a subclass of this class
1306        self.exporttype = []  # defines the type(s) of exports that the class can handle.
1307        # The following types are defined: 'project', "phase", "powder", "single"
1308        self.multiple = False # set as True if the class can export multiple phases or histograms
1309        # self.multiple is ignored for "project" exports
1310
1311    def InitExport(self,event):
1312        '''Determines the type of menu that called the Exporter and
1313        misc initialization.
1314        '''
1315        self.filename = None # name of file to be written (single export)
1316        self.dirname = '' # name of file to be written (multiple export)
1317        if event:
1318            self.currentExportType = self.G2frame.ExportLookup.get(event.Id)
1319
1320    def MakePWDRfilename(self,hist):
1321        '''Make a filename root (no extension) from a PWDR histogram name
1322
1323        :param str hist: the histogram name in data tree (starts with "PWDR ")
1324        '''
1325        file0 = ''
1326        file1 = hist[5:]
1327        # replace repeated blanks
1328        while file1 != file0:
1329            file0 = file1
1330            file1 = file0.replace('  ',' ').strip()
1331        file0 = file1.replace('Azm= ','A')
1332        # if angle has unneeded decimal places on aziumuth, remove them
1333        if file0[-3:] == '.00': file0 = file0[:-3]
1334        file0 = file0.replace('.','_')
1335        file0 = file0.replace(' ','_')
1336        return file0
1337
1338    def ExportSelect(self,AskFile='ask'):
1339        '''Selects histograms or phases when needed. Sets a default file name when
1340        requested into self.filename; always sets a default directory in self.dirname.
1341
1342        :param bool AskFile: Determines how this routine processes getting a
1343          location to store the current export(s).
1344         
1345          * if AskFile is 'ask' (default option), get the name of the file to be written;
1346            self.filename and self.dirname are always set. In the case where
1347            multiple files must be generated, the export routine should do this
1348            based on self.filename as a template.
1349          * if AskFile is 'dir', get the name of the directory to be used;
1350            self.filename is not used, but self.dirname is always set. The export routine
1351            will always generate the file name.
1352          * if AskFile is 'single', get only the name of the directory to be used when
1353            multiple items will be written (as multiple files) are used
1354            *or* a complete file name is requested when a single file
1355            name is selected. self.dirname is always set and self.filename used
1356            only when a single file is selected. 
1357          * if AskFile is 'default', creates a name of the file to be used from
1358            the name of the project (.gpx) file. If the project has not been saved,
1359            then the name of file is requested.
1360            self.filename and self.dirname are always set. In the case where
1361            multiple file names must be generated, the export routine should do this
1362            based on self.filename.
1363          * if AskFile is 'default-dir', sets self.dirname from the project (.gpx)
1364            file. If the project has not been saved, then a directory is requested.
1365            self.filename is not used.
1366
1367        :returns: True in case of an error
1368        '''
1369       
1370        numselected = 1
1371        if self.currentExportType == 'phase':
1372            if len(self.Phases) == 0:
1373                self.G2frame.ErrorDialog(
1374                    'Empty project',
1375                    'Project does not contain any phases.')
1376                return True
1377            elif len(self.Phases) == 1:
1378                self.phasenam = list(self.Phases.keys())
1379            elif self.multiple: 
1380                choices = sorted(self.Phases.keys())
1381                phasenum = G2G.ItemSelector(choices,self.G2frame,multiple=True)
1382                if phasenum is None: return True
1383                self.phasenam = [choices[i] for i in phasenum]
1384                if not self.phasenam: return True
1385                numselected = len(self.phasenam)
1386            else:
1387                choices = sorted(self.Phases.keys())
1388                phasenum = G2G.ItemSelector(choices,self.G2frame)
1389                if phasenum is None: return True
1390                self.phasenam = [choices[phasenum]]
1391                numselected = len(self.phasenam)
1392        elif self.currentExportType == 'single':
1393            if len(self.xtalDict) == 0:
1394                self.G2frame.ErrorDialog(
1395                    'Empty project',
1396                    'Project does not contain any single crystal data.')
1397                return True
1398            elif len(self.xtalDict) == 1:
1399                self.histnam = self.xtalDict.values()
1400            elif self.multiple:
1401                choices = sorted(self.xtalDict.values())
1402                hnum = G2G.ItemSelector(choices,self.G2frame,multiple=True)
1403                if not hnum: return True
1404                self.histnam = [choices[i] for i in hnum]
1405                numselected = len(self.histnam)
1406            else:
1407                choices = sorted(self.xtalDict.values())
1408                hnum = G2G.ItemSelector(choices,self.G2frame)
1409                if hnum is None: return True
1410                self.histnam = [choices[hnum]]
1411                numselected = len(self.histnam)
1412        elif self.currentExportType == 'powder':
1413            if len(self.powderDict) == 0:
1414                self.G2frame.ErrorDialog(
1415                    'Empty project',
1416                    'Project does not contain any powder data.')
1417                return True
1418            elif len(self.powderDict) == 1:
1419                self.histnam = self.powderDict.values()
1420            elif self.multiple:
1421                choices = sorted(self.powderDict.values())
1422                hnum = G2G.ItemSelector(choices,self.G2frame,multiple=True)
1423                if not hnum: return True
1424                self.histnam = [choices[i] for i in hnum]
1425                numselected = len(self.histnam)
1426            else:
1427                choices = sorted(self.powderDict.values())
1428                hnum = G2G.ItemSelector(choices,self.G2frame)
1429                if hnum is None: return True
1430                self.histnam = [choices[hnum]]
1431                numselected = len(self.histnam)
1432        elif self.currentExportType == 'sasd':
1433            if len(self.sasdDict) == 0:
1434                self.G2frame.ErrorDialog(
1435                    'Empty project',
1436                    'Project does not contain any small angle data.')
1437                return True
1438            elif len(self.sasdDict) == 1:
1439                self.histnam = self.sasdDict.values()
1440            elif self.multiple:
1441                choices = sorted(self.sasdDict.values())
1442                hnum = G2G.ItemSelector(choices,self.G2frame,multiple=True)
1443                if not hnum: return True
1444                self.histnam = [choices[i] for i in hnum]
1445                numselected = len(self.histnam)
1446            else:
1447                choices = sorted(self.sasdDict.values())
1448                hnum = G2G.ItemSelector(choices,self.G2frame)
1449                if hnum is None: return True
1450                self.histnam = [choices[hnum]]
1451                numselected = len(self.histnam)
1452        elif self.currentExportType == 'image':
1453            if len(self.Histograms) == 0:
1454                self.G2frame.ErrorDialog(
1455                    'Empty project',
1456                    'Project does not contain any images.')
1457                return True
1458            elif len(self.Histograms) == 1:
1459                self.histnam = list(self.Histograms.keys())
1460            else:
1461                choices = sorted(self.Histograms.keys())
1462                hnum = G2G.ItemSelector(choices,self.G2frame,multiple=self.multiple)
1463                if self.multiple:
1464                    if not hnum: return True
1465                    self.histnam = [choices[i] for i in hnum]
1466                else:
1467                    if hnum is None: return True
1468                    self.histnam = [choices[hnum]]
1469                numselected = len(self.histnam)
1470        if self.currentExportType == 'map':
1471            # search for phases with maps
1472            mapPhases = []
1473            choices = []
1474            for phasenam in sorted(self.Phases):
1475                phasedict = self.Phases[phasenam] # pointer to current phase info           
1476                if len(phasedict['General']['Map'].get('rho',[])):
1477                    mapPhases.append(phasenam)
1478                    if phasedict['General']['Map'].get('Flip'):
1479                        choices.append('Charge flip map: '+str(phasenam))
1480                    elif phasedict['General']['Map'].get('MapType'):
1481                        choices.append(
1482                            str(phasedict['General']['Map'].get('MapType'))
1483                            + ' map: ' + str(phasenam))
1484                    else:
1485                        choices.append('unknown map: '+str(phasenam))
1486            # select a map if needed
1487            if len(mapPhases) == 0:
1488                self.G2frame.ErrorDialog(
1489                    'Empty project',
1490                    'Project does not contain any maps.')
1491                return True
1492            elif len(mapPhases) == 1:
1493                self.phasenam = mapPhases
1494            else: 
1495                phasenum = G2G.ItemSelector(choices,self.G2frame,multiple=self.multiple)
1496                if self.multiple:
1497                    if not phasenum: return True
1498                    self.phasenam = [mapPhases[i] for i in phasenum]
1499                else:
1500                    if phasenum is None: return True
1501                    self.phasenam = [mapPhases[phasenum]]
1502            numselected = len(self.phasenam)
1503
1504        # items selected, now set self.dirname and usually self.filename
1505        if AskFile == 'ask' or (AskFile == 'single' and numselected == 1) or (
1506            AskFile == 'default' and not self.G2frame.GSASprojectfile
1507            ):
1508            filename = self.askSaveFile()
1509            if not filename: return True
1510            self.dirname,self.filename = os.path.split(filename)
1511        elif AskFile == 'dir' or AskFile == 'single' or (
1512            AskFile == 'default-dir' and not self.G2frame.GSASprojectfile
1513            ):
1514            self.dirname = self.askSaveDirectory()
1515            if not self.dirname: return True
1516        elif AskFile == 'default-dir' or AskFile == 'default':
1517            self.dirname,self.filename = os.path.split(
1518                os.path.splitext(self.G2frame.GSASprojectfile)[0] + self.extension
1519                )
1520        else:
1521            raise Exception('This should not happen!')
1522
1523    def loadParmDict(self):
1524        '''Load the GSAS-II refinable parameters from the tree into a dict (self.parmDict). Update
1525        refined values to those from the last cycle and set the uncertainties for the
1526        refined parameters in another dict (self.sigDict).
1527
1528        Expands the parm & sig dicts to include values derived from constraints.
1529
1530        This could be made faster for sequential fits by reducing the histogram list to only
1531        the active histogram being exported.
1532        '''
1533        self.G2frame.CheckNotebook()
1534        self.parmDict = {}
1535        self.sigDict = {}
1536        rigidbodyDict = {}
1537        covDict = {}
1538        consDict = {}
1539        Histograms,Phases = self.G2frame.GetUsedHistogramsAndPhasesfromTree()
1540        if self.G2frame.GPXtree.IsEmpty(): return # nothing to do
1541        item, cookie = self.G2frame.GPXtree.GetFirstChild(self.G2frame.root)
1542        while item:
1543            name = self.G2frame.GPXtree.GetItemText(item)
1544            if name == 'Rigid bodies':
1545                 rigidbodyDict = self.G2frame.GPXtree.GetItemPyData(item)
1546            elif name == 'Covariance':
1547                 covDict = self.G2frame.GPXtree.GetItemPyData(item)
1548            elif name == 'Constraints':
1549                 consDict = self.G2frame.GPXtree.GetItemPyData(item)
1550            item, cookie = self.G2frame.GPXtree.GetNextChild(self.G2frame.root, cookie)
1551        rbVary,rbDict =  G2stIO.GetRigidBodyModels(rigidbodyDict,Print=False)
1552        self.parmDict.update(rbDict)
1553        rbIds = rigidbodyDict.get('RBIds',{'Vector':[],'Residue':[]})
1554        Natoms,atomIndx,phaseVary,phaseDict,pawleyLookup,FFtables,BLtables,MFtables,maxSSwave =  G2stIO.GetPhaseData(
1555            Phases,RestraintDict=None,rbIds=rbIds,Print=False)
1556        self.parmDict.update(phaseDict)
1557        hapVary,hapDict,controlDict =  G2stIO.GetHistogramPhaseData(
1558            Phases,Histograms,Print=False,resetRefList=False)
1559        self.parmDict.update(hapDict)
1560        histVary,histDict,controlDict =  G2stIO.GetHistogramData(Histograms,Print=False)
1561        self.parmDict.update(histDict)
1562        self.parmDict.update(zip(
1563            covDict.get('varyList',[]),
1564            covDict.get('variables',[])))
1565        self.sigDict = dict(zip(
1566            covDict.get('varyList',[]),
1567            covDict.get('sig',[])))
1568        # expand to include constraints: first compile a list of constraints
1569        constList = []
1570        for item in consDict:
1571            if item.startswith('_'): continue
1572            constList += consDict[item]
1573        # now process the constraints
1574        G2mv.InitVars()
1575        constDict,fixedList,ignored = G2stIO.ProcessConstraints(constList)
1576        varyList = covDict.get('varyListStart')
1577        if varyList is None and len(constDict) == 0:
1578            # no constraints can use varyList
1579            varyList = covDict.get('varyList')
1580        elif varyList is None:
1581            # old GPX file from before pre-constraint varyList is saved
1582            print (' *** Old refinement: Please use Calculate/Refine to redo  ***')
1583            raise Exception(' *** Export aborted ***')
1584        else:
1585            varyList = list(varyList)
1586        # add symmetry-generated constraints
1587        rigidbodyDict = self.G2frame.GPXtree.GetItemPyData(   
1588            G2gd.GetGPXtreeItemId(self.G2frame,self.G2frame.root,'Rigid bodies'))
1589        rbIds = rigidbodyDict.get('RBIds',{'Vector':[],'Residue':[]})
1590        rbVary,rbDict = G2stIO.GetRigidBodyModels(rigidbodyDict,Print=False)
1591        Natoms,atomIndx,phaseVary,phaseDict,pawleyLookup,FFtables,BLtables,MFtables,maxSSwave = G2stIO.GetPhaseData(
1592            Phases,RestraintDict=None,rbIds=rbIds,Print=False) # generates atom symmetry constraints
1593        msg = G2mv.EvaluateMultipliers(constDict,phaseDict)
1594        if msg:
1595            print('Unable to interpret multiplier(s): '+msg)
1596            raise Exception(' *** CIF creation aborted ***')
1597        try:
1598            G2mv.GenerateConstraints(varyList,constDict,fixedList,self.parmDict)
1599            #print(G2mv.VarRemapShow(varyList))
1600        except:
1601            # this really should not happen
1602            print (' *** ERROR - constraints are internally inconsistent ***')
1603            errmsg, warnmsg = G2mv.CheckConstraints(varyList,constDict,fixedList)
1604            print ('Errors'+errmsg)
1605            if warnmsg: print ('Warnings'+warnmsg)
1606            raise Exception(' *** CIF creation aborted ***')
1607        # add the constrained values to the parameter dictionary
1608        G2mv.Dict2Map(self.parmDict,varyList)
1609        # and add their uncertainties into the esd dictionary (sigDict)
1610        if covDict.get('covMatrix') is not None:
1611            self.sigDict.update(G2mv.ComputeDepESD(covDict['covMatrix'],covDict['varyList'],self.parmDict))
1612
1613    def loadTree(self):
1614        '''Load the contents of the data tree into a set of dicts
1615        (self.OverallParms, self.Phases and self.Histogram as well as self.powderDict
1616        & self.xtalDict)
1617       
1618        * The childrenless data tree items are overall parameters/controls for the
1619          entire project and are placed in self.OverallParms
1620        * Phase items are placed in self.Phases
1621        * Data items are placed in self.Histogram. The key for these data items
1622          begin with a keyword, such as PWDR, IMG, HKLF,... that identifies the data type.
1623        '''
1624        self.OverallParms = {}
1625        self.powderDict = {}
1626        self.sasdDict = {}
1627        self.xtalDict = {}
1628        self.Phases = {}
1629        self.Histograms = {}
1630        self.SeqRefdata = None
1631        self.SeqRefhist = None
1632        if self.G2frame.GPXtree.IsEmpty(): return # nothing to do
1633        histType = None       
1634        if self.currentExportType == 'phase':
1635            # if exporting phases load them here
1636            sub = G2gd.GetGPXtreeItemId(self.G2frame,self.G2frame.root,'Phases')
1637            if not sub:
1638                print ('no phases found')
1639                return True
1640            item, cookie = self.G2frame.GPXtree.GetFirstChild(sub)
1641            while item:
1642                phaseName = self.G2frame.GPXtree.GetItemText(item)
1643                self.Phases[phaseName] =  self.G2frame.GPXtree.GetItemPyData(item)
1644                item, cookie = self.G2frame.GPXtree.GetNextChild(sub, cookie)
1645            return
1646        elif self.currentExportType == 'single':
1647            histType = 'HKLF'
1648        elif self.currentExportType == 'powder':
1649            histType = 'PWDR'
1650        elif self.currentExportType == 'image':
1651            histType = 'IMG'
1652        elif self.currentExportType == 'sasd':
1653            histType = 'SASD'
1654
1655        if histType: # Loading just one kind of tree entry
1656            item, cookie = self.G2frame.GPXtree.GetFirstChild(self.G2frame.root)
1657            while item:
1658                name = self.G2frame.GPXtree.GetItemText(item)
1659                if name.startswith(histType):
1660                    if self.Histograms.get(name): # there is already an item with this name
1661                        print('Histogram name '+str(name)+' is repeated. Renaming')
1662                        if name[-1] == '9':
1663                            name = name[:-1] + '10'
1664                        elif name[-1] in '012345678':
1665                            name = name[:-1] + str(int(name[-1])+1)
1666                        else:                           
1667                            name += '-1'
1668                    self.Histograms[name] = {}
1669                    # the main info goes into Data, but the 0th
1670                    # element contains refinement results, carry
1671                    # that over too now.
1672                    self.Histograms[name]['Data'] = self.G2frame.GPXtree.GetItemPyData(item)[1]
1673                    self.Histograms[name][0] = self.G2frame.GPXtree.GetItemPyData(item)[0]
1674                    item2, cookie2 = self.G2frame.GPXtree.GetFirstChild(item)
1675                    while item2: 
1676                        child = self.G2frame.GPXtree.GetItemText(item2)
1677                        self.Histograms[name][child] = self.G2frame.GPXtree.GetItemPyData(item2)
1678                        item2, cookie2 = self.G2frame.GPXtree.GetNextChild(item, cookie2)
1679                item, cookie = self.G2frame.GPXtree.GetNextChild(self.G2frame.root, cookie)
1680            # index powder and single crystal histograms by number
1681            for hist in self.Histograms:
1682                if hist.startswith("PWDR"): 
1683                    d = self.powderDict
1684                elif hist.startswith("HKLF"): 
1685                    d = self.xtalDict
1686                elif hist.startswith("SASD"):
1687                    d = self.sasdDict
1688                else:
1689                    return                   
1690                i = self.Histograms[hist].get('hId')
1691                if i is None and not d.keys():
1692                    i = 0
1693                elif i is None or i in d.keys():
1694                    i = max(d.keys())+1
1695                d[i] = hist
1696            return
1697        # else standard load: using all interlinked phases and histograms
1698        self.Histograms,self.Phases = self.G2frame.GetUsedHistogramsAndPhasesfromTree()
1699        item, cookie = self.G2frame.GPXtree.GetFirstChild(self.G2frame.root)
1700        while item:
1701            name = self.G2frame.GPXtree.GetItemText(item)
1702            item2, cookie2 = self.G2frame.GPXtree.GetFirstChild(item)
1703            if not item2: 
1704                self.OverallParms[name] = self.G2frame.GPXtree.GetItemPyData(item)
1705            item, cookie = self.G2frame.GPXtree.GetNextChild(self.G2frame.root, cookie)
1706        # index powder and single crystal histograms
1707        for hist in self.Histograms:
1708            i = self.Histograms[hist]['hId']
1709            if hist.startswith("PWDR"): 
1710                self.powderDict[i] = hist
1711            elif hist.startswith("HKLF"): 
1712                self.xtalDict[i] = hist
1713            elif hist.startswith("SASD"):
1714                self.sasdDict[i] = hist
1715
1716    def dumpTree(self,mode='type'):
1717        '''Print out information on the data tree dicts loaded in loadTree.
1718        Used for testing only.
1719        '''
1720        if self.SeqRefdata and self.SeqRefhist:
1721            print('Note that dumpTree does not show sequential results')
1722        print ('\nOverall')
1723        if mode == 'type':
1724            def Show(arg): return type(arg)
1725        else:
1726            def Show(arg): return arg
1727        for key in self.OverallParms:
1728            print ('  '+key+Show(self.OverallParms[key]))
1729        print ('Phases')
1730        for key1 in self.Phases:
1731            print ('    '+key1+Show(self.Phases[key1]))
1732        print ('Histogram')
1733        for key1 in self.Histograms:
1734            print ('    '+key1+Show(self.Histograms[key1]))
1735            for key2 in self.Histograms[key1]:
1736                print ('      '+key2+Show(self.Histograms[key1][key2]))
1737
1738    def defaultSaveFile(self):
1739        return os.path.abspath(
1740            os.path.splitext(self.G2frame.GSASprojectfile
1741                             )[0]+self.extension)
1742       
1743    def askSaveFile(self):
1744        '''Ask the user to supply a file name
1745
1746        :returns: a file name (str) or None if Cancel is pressed
1747
1748        '''
1749        pth = G2G.GetExportPath(self.G2frame)
1750        if self.G2frame.GSASprojectfile:
1751            defnam = os.path.splitext(
1752                os.path.split(self.G2frame.GSASprojectfile)[1]
1753                )[0]+self.extension
1754        else:
1755            defnam = 'default' + self.extension
1756        return G2G.askSaveFile(self.G2frame,defnam,self.extension,self.longFormatName)
1757
1758    def askSaveDirectory(self):
1759        '''Ask the user to supply a directory name. Path name is used as the
1760        starting point for the next export path search.
1761
1762        :returns: a directory name (str) or None if Cancel is pressed
1763
1764        TODO: Can this be replaced with G2G.askSaveDirectory?
1765        '''
1766        pth = G2G.GetExportPath(self.G2frame)
1767        dlg = wx.DirDialog(
1768            self.G2frame, 'Input directory where file(s) will be written', pth,
1769            wx.DD_DEFAULT_STYLE)
1770        dlg.CenterOnParent()
1771        try:
1772            if dlg.ShowModal() == wx.ID_OK:
1773                filename = dlg.GetPath()
1774                self.G2frame.LastExportDir = filename
1775            else:
1776                filename = None
1777        finally:
1778            dlg.Destroy()
1779        return filename
1780
1781    # Tools for file writing.
1782    def OpenFile(self,fil=None,mode='w'):
1783        '''Open the output file
1784
1785        :param str fil: The name of the file to open. If None (default)
1786          the name defaults to self.dirname + self.filename.
1787          If an extension is supplied, it is not overridded,
1788          but if not, the default extension is used.
1789        :returns: the file object opened by the routine which is also
1790          saved as self.fp
1791        '''
1792        if mode == 'd': # debug mode
1793            self.fullpath = '(stdout)'
1794            self.fp = sys.stdout
1795            return
1796        if not fil:
1797            if not os.path.splitext(self.filename)[1]:
1798                self.filename += self.extension
1799            fil = os.path.join(self.dirname,self.filename)
1800        self.fullpath = os.path.abspath(fil)
1801        self.fp = open(self.fullpath,mode)
1802        return self.fp
1803
1804    def Write(self,line):
1805        '''write a line of output, attaching a line-end character
1806
1807        :param str line: the text to be written.
1808        '''
1809        self.fp.write(line+'\n')
1810       
1811    def CloseFile(self,fp=None):
1812        '''Close a file opened in OpenFile
1813
1814        :param file fp: the file object to be closed. If None (default)
1815          file object self.fp is closed.
1816        '''
1817        if self.fp == sys.stdout: return # debug mode
1818        if fp is None:
1819            fp = self.fp
1820            self.fp = None
1821        if fp is not None: fp.close()
1822       
1823    def SetSeqRef(self,data,hist):
1824        '''Set the exporter to retrieve results from a sequential refinement
1825        rather than the main tree
1826        '''
1827        self.SeqRefdata = data
1828        self.SeqRefhist = hist
1829        data_name = data[hist]
1830        for i,val in zip(data_name['varyList'],data_name['sig']):
1831            self.sigDict[i] = val
1832            self.sigDict[striphist(i)] = val
1833        for i in data_name['parmDict']:
1834            self.parmDict[striphist(i)] = data_name['parmDict'][i]
1835            self.parmDict[i] = data_name['parmDict'][i]
1836            # zero out the dA[xyz] terms, they would only bring confusion
1837            key = i.split(':')
1838            if len(key) < 3: continue
1839            if key[2].startswith('dA'):
1840                self.parmDict[i] = 0.0
1841        for i,(val,sig) in data_name.get('depParmDict',{}).items():
1842            self.parmDict[i] = val
1843            self.sigDict[i] = sig
1844        #GSASIIpath.IPyBreak()
1845
1846    # Tools to pull information out of the data arrays
1847    def GetCell(self,phasenam):
1848        """Gets the unit cell parameters and their s.u.'s for a selected phase
1849
1850        :param str phasenam: the name for the selected phase
1851        :returns: `cellList,cellSig` where each is a 7 element list corresponding
1852          to a, b, c, alpha, beta, gamma, volume where `cellList` has the
1853          cell values and `cellSig` has their uncertainties.
1854        """
1855        if self.SeqRefdata and self.SeqRefhist:
1856            return self.GetSeqCell(phasenam,self.SeqRefdata[self.SeqRefhist])
1857        phasedict = self.Phases[phasenam] # pointer to current phase info
1858        try:
1859            pfx = str(phasedict['pId'])+'::'
1860            A,sigA = G2stIO.cellFill(pfx,phasedict['General']['SGData'],self.parmDict,self.sigDict)
1861            cellSig = G2stIO.getCellEsd(pfx,phasedict['General']['SGData'],A,
1862                self.OverallParms['Covariance'])  # returns 7 vals, includes sigVol
1863            cellList = G2lat.A2cell(A) + (G2lat.calc_V(A),)
1864            return cellList,cellSig
1865        except KeyError:
1866            cell = phasedict['General']['Cell'][1:]
1867            return cell,7*[0]
1868           
1869    def GetSeqCell(self,phasenam,data_name):
1870        """Gets the unit cell parameters and their s.u.'s for a selected phase
1871        and histogram in a sequential fit
1872
1873        :param str phasenam: the name for the selected phase
1874        :param dict data_name: the sequential refinement parameters for the selected histogram
1875        :returns: `cellList,cellSig` where each is a 7 element list corresponding
1876          to a, b, c, alpha, beta, gamma, volume where `cellList` has the
1877          cell values and `cellSig` has their uncertainties.
1878        """
1879        phasedict = self.Phases[phasenam]
1880        SGdata = phasedict['General']['SGData']
1881        pId = phasedict['pId']
1882        RecpCellTerms = G2lat.cell2A(phasedict['General']['Cell'][1:7])
1883        ESDlookup = {}
1884        Dlookup = {}
1885        varied = [striphist(i) for i in data_name['varyList']]
1886        for item,val in data_name['newCellDict'].items():
1887            if item in varied:
1888                ESDlookup[val[0]] = item
1889                Dlookup[item] = val[0]
1890        A = RecpCellTerms[:]
1891        for i in range(6):
1892            var = str(pId)+'::A'+str(i)
1893            if var in ESDlookup:
1894                A[i] = data_name['newCellDict'][ESDlookup[var]][1] # override with refined value
1895        cellDict = dict(zip([str(pId)+'::A'+str(i) for i in range(6)],A))
1896        zeroDict = {i:0.0 for i in cellDict}
1897        A,zeros = G2stIO.cellFill(str(pId)+'::',SGdata,cellDict,zeroDict)
1898        covData = {
1899            'varyList': [Dlookup.get(striphist(v),v) for v in data_name['varyList']],
1900            'covMatrix': data_name['covMatrix']
1901            }
1902        return list(G2lat.A2cell(A)) + [G2lat.calc_V(A)], G2stIO.getCellEsd(str(pId)+'::',SGdata,A,covData)
1903               
1904    def GetAtoms(self,phasenam):
1905        """Gets the atoms associated with a phase. Can be used with standard
1906        or macromolecular phases
1907
1908        :param str phasenam: the name for the selected phase
1909        :returns: a list of items for eac atom where each item is a list containing:
1910          label, typ, mult, xyz, and td, where
1911
1912          * label and typ are the atom label and the scattering factor type (str)
1913          * mult is the site multiplicity (int)
1914          * xyz is contains a list with four pairs of numbers:
1915            x, y, z and fractional occupancy and
1916            their standard uncertainty (or a negative value)
1917          * td is contains a list with either one or six pairs of numbers:
1918            if one number it is U\ :sub:`iso` and with six numbers it is
1919            U\ :sub:`11`, U\ :sub:`22`, U\ :sub:`33`, U\ :sub:`12`, U\ :sub:`13` & U\ :sub:`23`
1920            paired with their standard uncertainty (or a negative value)
1921        """
1922        phasedict = self.Phases[phasenam] # pointer to current phase info           
1923        cx,ct,cs,cia = phasedict['General']['AtomPtrs']
1924        cfrac = cx+3
1925        fpfx = str(phasedict['pId'])+'::Afrac:'       
1926        atomslist = []
1927        for i,at in enumerate(phasedict['Atoms']):
1928            if phasedict['General']['Type'] == 'macromolecular':
1929                label = '%s_%s_%s_%s'%(at[ct-1],at[ct-3],at[ct-4],at[ct-2])
1930            else:
1931                label = at[ct-1]
1932            fval = self.parmDict.get(fpfx+str(i),at[cfrac])
1933            fsig = self.sigDict.get(fpfx+str(i),-0.009)
1934            mult = at[cs+1]
1935            typ = at[ct]
1936            xyz = []
1937            for j,v in enumerate(('x','y','z')):
1938                val = at[cx+j]
1939                pfx = str(phasedict['pId']) + '::A' + v + ':' + str(i)
1940                val = self.parmDict.get(pfx, val)
1941                dpfx = str(phasedict['pId'])+'::dA'+v+':'+str(i)
1942                sig = self.sigDict.get(dpfx,-0.000009)
1943                xyz.append((val,sig))
1944            xyz.append((fval,fsig))
1945            td = []
1946            if at[cia] == 'I':
1947                pfx = str(phasedict['pId'])+'::AUiso:'+str(i)
1948                val = self.parmDict.get(pfx,at[cia+1])
1949                sig = self.sigDict.get(pfx,-0.0009)
1950                td.append((val,sig))
1951            else:
1952                for i,var in enumerate(('AU11','AU22','AU33','AU12','AU13','AU23')):
1953                    pfx = str(phasedict['pId'])+'::'+var+':'+str(i)
1954                    val = self.parmDict.get(pfx,at[cia+2+i])
1955                    sig = self.sigDict.get(pfx,-0.0009)
1956                    td.append((val,sig))
1957            atomslist.append((label,typ,mult,xyz,td))
1958        return atomslist
1959######################################################################
1960def ExportPowderList(G2frame):
1961    '''Returns a list of extensions supported by :func:`GSASIIIO:ExportPowder`
1962    This is used in :meth:`GSASIIimgGUI.AutoIntFrame` only.
1963   
1964    :param wx.Frame G2frame: the GSAS-II main data tree window
1965    '''
1966    extList = []
1967    for obj in G2frame.exporterlist:
1968        if 'powder' in obj.exporttype:
1969            try:
1970                obj.Writer
1971                extList.append(obj.extension)
1972            except AttributeError:
1973                pass
1974    return extList
1975
1976def ExportPowder(G2frame,TreeName,fileroot,extension):
1977    '''Writes a single powder histogram using the Export routines.
1978    This is used in :meth:`GSASIIimgGUI.AutoIntFrame` only.
1979
1980    :param wx.Frame G2frame: the GSAS-II main data tree window
1981    :param str TreeName: the name of the histogram (PWDR ...) in the data tree
1982    :param str fileroot: name for file to be written, extension ignored
1983    :param str extension: extension for file to be written (start with '.'). Must
1984      match a powder export routine that has a Writer object.
1985    '''
1986    filename = os.path.abspath(os.path.splitext(fileroot)[0]+extension)
1987    for obj in G2frame.exporterlist:
1988        if obj.extension == extension and 'powder' in obj.exporttype:
1989            obj.currentExportType = 'powder'
1990            obj.InitExport(None)
1991            obj.loadTree() # load all histograms in tree into dicts
1992            if TreeName not in obj.Histograms:
1993                raise Exception('Histogram not found: '+str(TreeName))
1994            try:
1995                obj.Writer
1996            except AttributeError:
1997                continue
1998            try:
1999                obj.Writer(TreeName,filename)
2000                print('wrote file '+filename)
2001                return
2002            except Exception:
2003                print('Export Routine for '+extension+' failed.')
2004    else:
2005        print('No Export routine supports extension '+extension)
2006
2007def ExportSequential(G2frame,data,obj,exporttype):
2008    '''
2009    Used to export from every phase/dataset in a sequential refinement using
2010    a .Writer method for either projects or phases. Prompts to select histograms
2011    and for phase exports, which phase(s).
2012
2013    :param wx.Frame G2frame: the GSAS-II main data tree window
2014    :param dict data: the sequential refinement data object
2015    :param str exporttype: indicates the type of export ('project' or 'phase')
2016    '''
2017    if len(data['histNames']) == 0:
2018        G2G.G2MessageBox(G2frame,'There are no sequential histograms','Warning')
2019    obj.InitExport(None)
2020    obj.loadTree()
2021    obj.loadParmDict()
2022    if len(data['histNames']) == 1:
2023        histlist = data['histNames']
2024    else:
2025        dlg = G2G.G2MultiChoiceDialog(G2frame,'Select histograms to export from list',
2026                                 'Select histograms',data['histNames'])
2027        if dlg.ShowModal() == wx.ID_OK:
2028            histlist = [data['histNames'][l] for l in dlg.GetSelections()]
2029            dlg.Destroy()
2030        else:
2031            dlg.Destroy()
2032            return
2033    if exporttype == 'Phase':
2034        phaselist = list(obj.Phases.keys())
2035        if len(obj.Phases) == 0:
2036            G2G.G2MessageBox(G2frame,'There are no phases in sequential ref.','Warning')
2037            return
2038        elif len(obj.Phases) > 1:
2039            dlg = G2G.G2MultiChoiceDialog(G2frame,'Select phases to export from list',
2040                                    'Select phases', phaselist)
2041            if dlg.ShowModal() == wx.ID_OK:
2042                phaselist = [phaselist[l] for l in dlg.GetSelections()]
2043                dlg.Destroy()
2044            else:
2045                dlg.Destroy()
2046                return
2047        filename = obj.askSaveFile()
2048        if not filename: return True
2049        obj.dirname,obj.filename = os.path.split(filename)
2050        print('Writing output to file '+str(obj.filename)+"...")
2051        mode = 'w'
2052        for p in phaselist:
2053            for h in histlist:
2054                obj.SetSeqRef(data,h)
2055                #GSASIIpath.IPyBreak()
2056                obj.Writer(h,phasenam=p,mode=mode)
2057                mode = 'a'
2058        print('...done')
2059    elif exporttype == 'Project':  # note that the CIF exporter is not yet ready for this
2060        filename = obj.askSaveFile()
2061        if not filename: return True
2062        obj.dirname,obj.filename = os.path.split(filename)
2063        print('Writing output to file '+str(obj.filename)+"...")
2064        mode = 'w'
2065        for h in histlist:
2066            obj.SetSeqRef(data,h)
2067            obj.Writer(h,mode=mode)
2068            print('\t'+str(h)+' written')
2069            mode = 'a'
2070        print('...done')
2071    elif exporttype == 'Powder':
2072        filename = obj.askSaveFile()
2073        if not filename: return True
2074        obj.dirname,obj.filename = os.path.split(filename)
2075        print('Writing output to file '+str(obj.filename)+"...")
2076        mode = 'w'
2077        for h in histlist:
2078            obj.SetSeqRef(data,h)
2079            obj.Writer(h,mode=mode)
2080            print('\t'+str(h)+' written')
2081            mode = 'a'
2082        print('...done')
2083
2084def ReadDIFFaX(DIFFaXfile):
2085    print ('read '+DIFFaXfile)
2086    Layer = {'Laue':'-1','Cell':[False,1.,1.,1.,90.,90.,90,1.],'Width':[[10.,10.],[False,False]],
2087        'Layers':[],'Stacking':[],'Transitions':[],'Toler':0.01,'AtInfo':{}}
2088    df = open(DIFFaXfile,'r')
2089    lines = df.readlines()
2090    df.close()
2091    struct = False
2092    Struct = []
2093    stack = False
2094    Stack = []
2095    trans = False
2096    Trans = []
2097    for diff in lines:
2098        diff = diff[:-1].lower()
2099        if '!'  in diff:
2100            continue
2101        while '}' in diff: #strip comments
2102            iB = diff.index('{')
2103            iF = diff.index('}')+1
2104            if iB:
2105                diff = diff[:iB]
2106            else:
2107                diff = diff[iF:]
2108        if not diff:
2109            continue
2110        if diff.strip() == 'instrumental':
2111            continue
2112        if diff.strip() == 'structural':
2113            struct = True
2114            continue
2115        elif diff.strip() == 'stacking':
2116            struct = False
2117            stack = True
2118            continue
2119        elif diff.strip() == 'transitions':
2120            stack = False
2121            trans = True
2122            continue
2123        diff = diff.strip()
2124        if struct:
2125            if diff:
2126                Struct.append(diff)
2127        elif stack:
2128            if diff:
2129                Stack.append(diff)
2130        elif trans:
2131            if diff:
2132                Trans.append(diff)
2133   
2134#STRUCTURE records
2135    laueRec = Struct[1].split()
2136    Layer['Laue'] = laueRec[0]
2137    if Layer['Laue'] == 'unknown' and len(laueRec) > 1:
2138        Layer['Toler'] = float(laueRec[1])    #tolerance for 'unknown'?
2139    if Layer['Laue'] == '2/m(1)': Layer['Laue'] = '2/m(c)'
2140    if Layer['Laue'] == '2/m(2)': Layer['Laue'] = '2/m(ab)'
2141    cell = Struct[0].split()
2142    Layer['Cell'] = [False,float(cell[0]),float(cell[1]),float(cell[2]),90.,90.,float(cell[3]),1.0]
2143    nLayers = int(Struct[2])
2144    N = 3
2145    if 'layer' not in Struct[3]:
2146        N = 4
2147        if Struct[3] != 'infinite':
2148            width = Struct[3].split()
2149            Layer['Width'][0] = [float(width[0]),float(width[1])]
2150    for nL in range(nLayers):
2151        if '=' in Struct[N]:
2152            name = Struct[N].split('=')
2153            sameas = int(name[1])-1
2154            Layer['Layers'].append({'Name':name[0],'SameAs':Layer['Layers'][sameas]['Name'],'Symm':'None','Atoms':[]})
2155            N += 1
2156            continue
2157        Symm = 'None'
2158        if 'centro' in Struct[N+1]: Symm = '-1'
2159        Layer['Layers'].append({'Name':Struct[N],'SameAs':'','Symm':Symm,'Atoms':[]})
2160        N += 2
2161        while 'layer' not in Struct[N]:
2162            atom = Struct[N][4:].split()
2163            atomType = G2el.FixValence(Struct[N][:4].replace(' ','').strip().capitalize())
2164            if atomType not in Layer['AtInfo']:
2165                Layer['AtInfo'][atomType] = G2el.GetAtomInfo(atomType)
2166            atomName = '%s(%s)'%(atomType,atom[0])
2167            newVals = []
2168            for val in atom[1:6]:
2169                if '/' in val:
2170                    newVals.append(eval(val+'.'))
2171                else:
2172                    newVals.append(float(val))               
2173            atomRec = [atomName,atomType,newVals[0],newVals[1],newVals[2],newVals[4],newVals[3]/78.9568]
2174            Layer['Layers'][-1]['Atoms'].append(atomRec)
2175            N += 1
2176            if N > len(Struct)-1:
2177                break
2178#TRANSITIONS records
2179    transArray = []
2180    N = 0
2181    for i in range(nLayers):
2182        transArray.append([])
2183        for j in range(nLayers):
2184            vals = Trans[N].split()
2185            newVals = []
2186            for val in vals[:4]:
2187                if '/' in val:
2188                    newVals.append(eval(val+'.'))
2189                else:
2190                    newVals.append(float(val))
2191            transArray[-1].append(newVals+['',False])
2192            N += 1
2193    Layer['Transitions'] = transArray
2194#STACKING records
2195    Layer['Stacking'] = [Stack[0],'']
2196    if Stack[0] == 'recursive':
2197        Layer['Stacking'][1] = Stack[1]
2198    elif Stack[0] == 'explicit':
2199        if Stack[1] == 'random':
2200            Layer['Stacking'][1] = Stack[1]
2201        else:
2202            Layer['Stacking'][1] = 'list'
2203            Layer['Stacking'].append('')
2204            for stack in Stack[2:]:
2205                Layer['Stacking'][2] += ' '+stack
2206    return Layer
2207
2208if __name__ == '__main__':
2209    import GSASIIdataGUI
2210    application = GSASIIdataGUI.GSASIImain(0)
2211    G2frame = application.main
2212    #app = wx.PySimpleApp()
2213    #G2frame = wx.Frame(None) # create a frame
2214    #frm.Show(True)
2215    #filename = '/tmp/notzip.zip'
2216    #filename = '/tmp/all.zip'
2217    #filename = '/tmp/11bmb_7652.zip'
2218   
2219    #selection=None, confirmoverwrite=True, parent=None
2220    #print ExtractFileFromZip(filename, selection='11bmb_7652.fxye',parent=frm)
2221    #print ExtractFileFromZip(filename,multipleselect=True)
2222    #                         #confirmread=False, confirmoverwrite=False)
2223
2224    # choicelist=[ ('a','b','c'),
2225    #              ('test1','test2'),('no choice',)]
2226    # titles = [ 'a, b or c', 'tests', 'No option here']
2227    # dlg = MultipleChoicesDialog(
2228    #     choicelist,titles,
2229    #     parent=frm)
2230    # if dlg.ShowModal() == wx.ID_OK:
2231    #     print 'Got OK'
2232    imagefile = '/tmp/NDC5_00237_3.ge3'
2233    Comments, Data, Npix, Image = GetImageData(G2frame,imagefile,imageOnly=False,ImageTag=None)
2234
2235    print("\n\nResults loaded to Comments, Data, Npix and Image\n\n")
2236
2237    GSASIIpath.IPyBreak_base()
Note: See TracBrowser for help on using the repository browser.