source: trunk/GSASIIconstrGUI.py @ 1545

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

fix tab selection (again); clean up some menus

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Author Revision URL Id
File size: 78.8 KB
Line 
1# -*- coding: utf-8 -*-
2#GSASIIconstrGUI - constraint GUI routines
3########### SVN repository information ###################
4# $Date: 2014-10-27 19:06:37 +0000 (Mon, 27 Oct 2014) $
5# $Author: toby $
6# $Revision: 1545 $
7# $URL: trunk/GSASIIconstrGUI.py $
8# $Id: GSASIIconstrGUI.py 1545 2014-10-27 19:06:37Z toby $
9########### SVN repository information ###################
10'''
11*GSASIIconstrGUI: Constraint GUI routines*
12------------------------------------------
13
14Used to define constraints and rigid bodies.
15
16'''
17import sys
18import wx
19import wx.grid as wg
20import wx.lib.scrolledpanel as wxscroll
21import time
22import random as ran
23import numpy as np
24import numpy.ma as ma
25import os.path
26import GSASIIpath
27GSASIIpath.SetVersionNumber("$Revision: 1545 $")
28import GSASIIElem as G2elem
29import GSASIIElemGUI as G2elemGUI
30import GSASIIstrIO as G2stIO
31import GSASIImapvars as G2mv
32import GSASIIgrid as G2gd
33import GSASIIplot as G2plt
34import GSASIIobj as G2obj
35VERY_LIGHT_GREY = wx.Colour(235,235,235)
36
37class MultiIntegerDialog(wx.Dialog):
38    '''Input a series of integers based on prompts
39    '''
40    def __init__(self,parent,title,prompts,values):
41        wx.Dialog.__init__(self,parent,-1,title, 
42            pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE)
43        self.panel = wx.Panel(self)         #just a dummy - gets destroyed in Draw!
44        self.values = values
45        self.prompts = prompts
46        self.Draw()
47       
48    def Draw(self):
49       
50        def OnValItem(event):
51            Obj = event.GetEventObject()
52            ind = Indx[Obj.GetId()]
53            try:
54                val = int(Obj.GetValue())
55                if val <= 0:
56                    raise ValueError
57            except ValueError:
58                val = self.values[ind]
59            self.values[ind] = val
60            Obj.SetValue('%d'%(val))
61           
62        self.panel.Destroy()
63        self.panel = wx.Panel(self)
64        mainSizer = wx.BoxSizer(wx.VERTICAL)
65        Indx = {}
66        for ival,[prompt,value] in enumerate(zip(self.prompts,self.values)):
67            mainSizer.Add(wx.StaticText(self.panel,-1,prompt),0,wx.ALIGN_CENTER)
68            valItem = wx.TextCtrl(self.panel,-1,value='%d'%(value),style=wx.TE_PROCESS_ENTER)
69            mainSizer.Add(valItem,0,wx.ALIGN_CENTER)
70            Indx[valItem.GetId()] = ival
71            valItem.Bind(wx.EVT_TEXT_ENTER,OnValItem)
72            valItem.Bind(wx.EVT_KILL_FOCUS,OnValItem)
73        OkBtn = wx.Button(self.panel,-1,"Ok")
74        OkBtn.Bind(wx.EVT_BUTTON, self.OnOk)
75        CancelBtn = wx.Button(self.panel,-1,'Cancel')
76        CancelBtn.Bind(wx.EVT_BUTTON, self.OnCancel)
77        btnSizer = wx.BoxSizer(wx.HORIZONTAL)
78        btnSizer.Add((20,20),1)
79        btnSizer.Add(OkBtn)
80        btnSizer.Add(CancelBtn)
81        btnSizer.Add((20,20),1)
82        mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP, 10)
83        self.panel.SetSizer(mainSizer)
84        self.panel.Fit()
85        self.Fit()
86
87    def GetValues(self):
88        return self.values
89       
90    def OnOk(self,event):
91        parent = self.GetParent()
92        parent.Raise()
93        self.EndModal(wx.ID_OK)             
94       
95    def OnCancel(self,event):
96        parent = self.GetParent()
97        parent.Raise()
98        self.EndModal(wx.ID_CANCEL)
99
100class ConstraintDialog(wx.Dialog):
101    '''Window to edit Constraint values
102    '''
103    def __init__(self,parent,title,text,data,separator='*',varname="",varyflag=False):
104        wx.Dialog.__init__(self,parent,-1,'Edit '+title, 
105            pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE)
106        self.data = data[:]
107        self.newvar = [varname,varyflag]
108        panel = wx.Panel(self)
109        mainSizer = wx.BoxSizer(wx.VERTICAL)
110        topLabl = wx.StaticText(panel,-1,text)
111        mainSizer.Add((10,10),1)
112        mainSizer.Add(topLabl,0,wx.ALIGN_CENTER_VERTICAL|wx.LEFT,10)
113        mainSizer.Add((10,10),1)
114        dataGridSizer = wx.FlexGridSizer(cols=3,hgap=2,vgap=2)
115        self.OkBtn = wx.Button(panel,wx.ID_OK)
116        for id in range(len(self.data)):
117            lbl1 = lbl = str(self.data[id][1])
118            if lbl[-1] != '=': lbl1 = lbl + ' ' + separator + ' '
119            name = wx.StaticText(panel,wx.ID_ANY,lbl1,
120                                 style=wx.ALIGN_RIGHT)
121            scale = G2gd.ValidatedTxtCtrl(panel,self.data[id],0,
122                                          typeHint=float,
123                                          OKcontrol=self.DisableOK)
124            dataGridSizer.Add(name,0,wx.LEFT|wx.RIGHT|wx.ALIGN_CENTER_VERTICAL,5)
125            dataGridSizer.Add(scale,0,wx.RIGHT,3)
126            if ':' in lbl:
127                dataGridSizer.Add(
128                    wx.StaticText(panel,-1,G2obj.fmtVarDescr(lbl)),
129                    0,wx.RIGHT|wx.ALIGN_CENTER_VERTICAL,3)
130            else:
131                dataGridSizer.Add((-1,-1))
132        if title == 'New Variable':
133            name = wx.StaticText(panel,wx.ID_ANY,"New variable's\nname (optional)",
134                                 style=wx.ALIGN_CENTER)
135            scale = G2gd.ValidatedTxtCtrl(panel,self.newvar,0,
136                                          typeHint=str,notBlank=False)
137            dataGridSizer.Add(name,0,wx.LEFT|wx.RIGHT|wx.ALIGN_CENTER_VERTICAL,5)
138            dataGridSizer.Add(scale,0,wx.RIGHT|wx.ALIGN_CENTER_VERTICAL,3)
139            self.refine = wx.CheckBox(panel,label='Refine?')
140            self.refine.SetValue(self.newvar[1]==True)
141            self.refine.Bind(wx.EVT_CHECKBOX, self.OnCheckBox)
142            dataGridSizer.Add(self.refine,0,wx.RIGHT|wx.ALIGN_CENTER_VERTICAL,3)
143           
144        mainSizer.Add(dataGridSizer,0,wx.EXPAND)
145        self.OkBtn.Bind(wx.EVT_BUTTON, self.OnOk)
146        self.OkBtn.SetDefault()
147        cancelBtn = wx.Button(panel,wx.ID_CANCEL)
148        cancelBtn.Bind(wx.EVT_BUTTON, self.OnCancel)
149        btnSizer = wx.BoxSizer(wx.HORIZONTAL)
150        btnSizer.Add((20,20),1)
151        btnSizer.Add(self.OkBtn)
152        btnSizer.Add((20,20),1)
153        btnSizer.Add(cancelBtn)
154        btnSizer.Add((20,20),1)
155
156        mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP, 10)
157        panel.SetSizer(mainSizer)
158        panel.Fit()
159        self.Fit()
160        self.CenterOnParent()
161       
162    def DisableOK(self,setting):
163        if setting:
164            self.OkBtn.Enable()
165        else:
166            self.OkBtn.Disable()
167
168    def OnCheckBox(self,event):
169        self.newvar[1] = self.refine.GetValue()
170
171    def OnOk(self,event):
172        parent = self.GetParent()
173        parent.Raise()
174        self.EndModal(wx.ID_OK)             
175
176    def OnCancel(self,event):
177        parent = self.GetParent()
178        parent.Raise()
179        self.EndModal(wx.ID_CANCEL)             
180
181    def GetData(self):
182        return self.data
183       
184################################################################################
185#####  Constraints
186################################################################################           
187       
188def UpdateConstraints(G2frame,data):
189    '''Called when Constraints tree item is selected.
190    Displays the constraints in the data window
191    '''
192    if not data:
193        data.update({'Hist':[],'HAP':[],'Phase':[],'Global':[]})       #empty dict - fill it
194    if 'Global' not in data:                                            #patch
195        data['Global'] = []
196    # DEBUG code ########################################
197    #import GSASIIconstrGUI
198    #reload(GSASIIconstrGUI)
199    #reload(G2obj)
200    #reload(G2stIO)
201    #import GSASIIstrMain
202    #reload(GSASIIstrMain)   
203    #reload(G2mv)
204    #reload(G2gd)
205    ###################################################
206    Histograms,Phases = G2frame.GetUsedHistogramsAndPhasesfromTree()
207    G2obj.IndexAllIds(Histograms,Phases)
208    ##################################################################################
209    # patch: convert old-style (str) variables in constraints to G2VarObj objects
210    for key,value in data.items():
211        if key.startswith('_'): continue
212        j = 0
213        for cons in value:
214            #print cons             # DEBUG
215            for i in range(len(cons[:-3])):
216                if type(cons[i][1]) is str:
217                    cons[i][1] = G2obj.G2VarObj(cons[i][1])
218                    j += 1
219        if j:
220            print str(key) + ': '+str(j)+' variable(s) as strings converted to objects'
221    ##################################################################################
222    rigidbodyDict = G2frame.PatternTree.GetItemPyData(
223        G2gd.GetPatternTreeItemId(G2frame,G2frame.root,'Rigid bodies'))
224    rbIds = rigidbodyDict.get('RBIds',{'Vector':[],'Residue':[]})
225    rbVary,rbDict = G2stIO.GetRigidBodyModels(rigidbodyDict,Print=False)
226    globalList = rbDict.keys()
227    globalList.sort()
228    try:
229        AtomDict = dict([Phases[phase]['pId'],Phases[phase]['Atoms']] for phase in Phases)
230    except KeyError:
231        G2frame.ErrorDialog('Constraint Error','You must run least squares at least once before setting constraints\n'+ \
232            'We suggest you refine scale factor first')
233        return
234
235    # create a list of the phase variables
236    Natoms,atomIndx,phaseVary,phaseDict,pawleyLookup,FFtable,BLtable = G2stIO.GetPhaseData(Phases,rbIds=rbIds,Print=False)
237    phaseList = []
238    for item in phaseDict:
239        if item.split(':')[2] not in ['Ax','Ay','Az','Amul','AI/A','Atype','SHorder']:
240            phaseList.append(item)
241    phaseList.sort()
242    phaseAtNames = {}
243    phaseAtTypes = {}
244    TypeList = []
245    for item in phaseList:
246        Split = item.split(':')
247        if Split[2][:2] in ['AU','Af','dA']:
248            Id = int(Split[0])
249            phaseAtNames[item] = AtomDict[Id][int(Split[3])][0]
250            phaseAtTypes[item] = AtomDict[Id][int(Split[3])][1]
251            if phaseAtTypes[item] not in TypeList:
252                TypeList.append(phaseAtTypes[item])
253        else:
254            phaseAtNames[item] = ''
255            phaseAtTypes[item] = ''
256             
257    # create a list of the hist*phase variables; include wildcards here
258    hapVary,hapDict,controlDict = G2stIO.GetHistogramPhaseData(Phases,Histograms,Print=False)
259    hapList = [i for i in hapDict.keys() if i.split(':')[2] not in ('Type',)]
260    hapList.sort()
261    wildList = [] # list of variables with "*" for histogram number
262    for i in hapList:
263        s = i.split(':')
264        if s[1] == "": continue
265        s[1] = '*'
266        sj = ':'.join(s)
267        if sj not in wildList: wildList.append(sj)
268    #wildList.sort() # unneeded
269    hapList += wildList
270    histVary,histDict,controlDict = G2stIO.GetHistogramData(Histograms,Print=False)
271    histList = []
272    for item in histDict:
273        if item.split(':')[2] not in ['Omega','Type','Chi','Phi',
274                                      'Azimuth','Gonio. radius',
275                                      'Lam1','Lam2','Back','Temperature','Pressure',
276                                      'FreePrm1','FreePrm2','FreePrm3',
277                                      ]:
278            histList.append(item)
279    histList.sort()
280    wildList = []
281    # for i in histList: # any reason to have this for hist constraints?
282    #     s = i.split(':')
283    #     if s[1] == "": continue
284    #     s[1] = '*'
285    #     sj = ':'.join(s)
286    #     if sj not in wildList: wildList.append(sj)
287    # histList += wildList
288    Indx = {}
289    G2frame.Page = [0,'phs']
290       
291    def FindEquivVarb(name,nameList):
292        'Creates a list of variables appropriate to constrain with name'
293        outList = []
294        #phlist = []
295        items = name.split(':')
296        namelist = [items[2],]
297        if 'dA' in name:
298            namelist = ['dAx','dAy','dAz']
299        elif 'AU' in name:
300            namelist = ['AUiso','AU11','AU22','AU33','AU12','AU13','AU23']
301        elif 'RB' in name:
302            rbfx = 'RB'+items[2][2]
303            if 'T' in name and 'Tr' not in name:
304                namelist = [rbfx+'T11',rbfx+'T22',rbfx+'T33',rbfx+'T12',rbfx+'T13',rbfx+'T23']
305            if 'L' in name:
306                namelist = [rbfx+'L11',rbfx+'L22',rbfx+'L33',rbfx+'L12',rbfx+'L13',rbfx+'L23']
307            if 'S' in name:
308                namelist = [rbfx+'S12',rbfx+'S13',rbfx+'S21',rbfx+'S23',rbfx+'S31',rbfx+'S32',rbfx+'SAA',rbfx+'SBB']
309            if 'U' in name:
310                namelist = [rbfx+'U',]
311
312        for item in nameList:
313            keys = item.split(':')
314            #if keys[0] not in phlist:
315            #    phlist.append(keys[0])
316            if items[1] == '*' and keys[2] in namelist: # wildcard -- select only sequential options
317                keys[1] = '*'
318                mitem = ':'.join(keys)
319                if mitem == name: continue
320                if mitem not in outList: outList.append(mitem)
321            elif keys[2] in namelist and item != name:
322                outList.append(item)
323        return outList
324       
325    def SelectVarbs(page,FrstVarb,varList,legend,constType):
326        '''Select variables used in constraints after one variable has
327        been selected. This routine determines the appropriate variables to be
328        used based on the one that has been selected and asks for more to be added.
329
330        It then creates the constraint and adds it to the constraints list.
331       
332        Called from OnAddEquivalence, OnAddFunction & OnAddConstraint (all but
333        OnAddHold)
334
335        :param list page: defines calling page -- type of variables to be used
336        :parm GSASIIobj.G2VarObj FrstVarb: reference to first selected variable
337        :param list varList: list of other appropriate variables to select from
338        :param str legend: header for selection dialog
339        :param str constType: type of constraint to be generated
340        :returns: a constraint, as defined in
341          :ref:`GSASIIobj <Constraint_definitions_table>`
342        '''
343        choices = [[i]+list(G2obj.VarDescr(i)) for i in varList]
344        meaning = G2obj.getDescr(FrstVarb.name)
345        if not meaning:
346            meaning = "(no definition found!)"
347        l = str(FrstVarb).split(':')
348        # make lists of phases & histograms to iterate over
349        phaselist = [l[0]]
350        if l[0]:
351            phaselbl = ['phase #'+l[0]]
352            if len(Phases) > 1:
353                phaselist += ['all'] 
354                phaselbl += ['all phases']
355        else:
356            phaselbl = ['']
357        histlist = [l[1]]
358        if l[1] == '*':
359            pass
360        elif l[1]:
361            histlbl = ['histogram #'+l[1]]
362            if len(Histograms) > 1:
363                histlist += ['all']
364                histlbl += ['all histograms']
365                typ = Histograms[G2obj.LookupHistName(l[1])[0]]['Instrument Parameters'][0]['Type'][1]
366                i = 0
367                for hist in Histograms:
368                    if Histograms[hist]['Instrument Parameters'][0]['Type'][1] == typ: i += 1
369                if i > 1:
370                    histlist += ['all='+typ]
371                    histlbl += ['all '+typ+' histograms']
372        else:
373            histlbl = ['']
374        # make a list of equivalent parameter names
375        nameList = [FrstVarb.name]
376        for var in varList:
377            nam = var.split(":")[2]
378            if nam not in nameList: nameList += [nam]
379        # add "wild-card" names to the list of variables
380        if l[1] == '*':
381            pass
382        elif page[1] == 'phs':
383            if 'RB' in FrstVarb.name:
384                pass
385            elif FrstVarb.atom is None:
386                for nam in nameList:
387                    for ph,plbl in zip(phaselist,phaselbl):
388                        if plbl: plbl = 'For ' + plbl
389                        var = ph+"::"+nam
390                        if var == str(FrstVarb) or var in varList: continue
391                        varList += [var]
392                        choices.append([var,plbl,meaning])
393            else:
394                for nam in nameList:
395                    for ph,plbl in zip(phaselist,phaselbl):
396                        if plbl: plbl = ' in ' + plbl
397                        for atype in ['']+TypeList:
398                            if atype:
399                                albl = "For "+atype+" atoms"
400                                akey = "all="+atype                       
401                            else:
402                                albl = "For all atoms"
403                                akey = "all"
404                            var = ph+"::"+nam+":"+akey
405                            if var == str(FrstVarb) or var in varList: continue
406                            varList += [var]
407                            choices.append([var,albl+plbl,meaning])
408        elif page[1] == 'hap':
409            if FrstVarb.name == "Scale":
410                meaning = "Phase fraction"
411            for nam in nameList:
412                for ph,plbl in zip(phaselist,phaselbl):
413                    if plbl: plbl = 'For ' + plbl
414                    for hst,hlbl in zip(histlist,histlbl):
415                        if hlbl:
416                            if plbl:
417                                hlbl = ' in ' + hlbl
418                            else:
419                                hlbl = 'For ' + hlbl                               
420                            var = ph+":"+hst+":"+nam
421                            if var == str(FrstVarb) or var in varList: continue
422                            varList += [var]
423                            choices.append([var,plbl+hlbl,meaning])
424        elif page[1] == 'hst':
425            if FrstVarb.name == "Scale":
426                meaning = "Scale factor"
427            for nam in nameList:
428                for hst,hlbl in zip(histlist,histlbl):
429                    if hlbl:
430                        hlbl = 'For ' + hlbl                               
431                        var = ":"+hst+":"+nam
432                        if var == str(FrstVarb) or var in varList: continue
433                        varList += [var]
434                        choices.append([var,hlbl,meaning])
435        elif page[1] == 'glb':
436            pass
437        else:
438            raise Exception, 'Unknown constraint page '+ page[1]                   
439        if len(choices):
440            l1 = l2 = 1
441            for i1,i2,i3 in choices:
442                l1 = max(l1,len(i1))
443                l2 = max(l2,len(i2))
444            fmt = "{:"+str(l1)+"s} {:"+str(l2)+"s} {:s}"
445            atchoice = [fmt.format(*i) for i in choices]
446            dlg = G2gd.G2MultiChoiceDialog(
447                G2frame.dataFrame,legend,
448                'Constrain '+str(FrstVarb)+' with...',atchoice,
449                toggle=False,size=(625,400),monoFont=True)
450            dlg.CenterOnParent()
451            res = dlg.ShowModal()
452            Selections = dlg.GetSelections()[:]
453            dlg.Destroy()
454            if res != wx.ID_OK: return []
455            if len(Selections) == 0:
456                dlg = wx.MessageDialog(
457                    G2frame.dataFrame,
458                    'No variables were selected to include with '+str(FrstVarb),
459                    'No variables')
460                dlg.CenterOnParent()
461                dlg.ShowModal()
462                dlg.Destroy()
463                return []
464        else:
465            dlg = wx.MessageDialog(
466                G2frame.dataFrame,
467                'There are no appropriate variables to include with '+str(FrstVarb),
468                'No variables')
469            dlg.CenterOnParent()
470            dlg.ShowModal()
471            dlg.Destroy()
472            return []
473        # now process the variables provided by the user
474        varbs = [str(FrstVarb),] # list of selected variables
475        for sel in Selections:
476            var = varList[sel]
477            # phase(s) included
478            l = var.split(':')
479            if l[0] == "all":
480                phlist = [str(Phases[phase]['pId']) for phase in Phases]
481            else:
482                phlist = [l[0]]
483            # histogram(s) included
484            if l[1] == "all":
485                hstlist = [str(Histograms[hist]['hId']) for hist in Histograms]
486            elif '=' in l[1]:
487                htyp = l[1].split('=')[1]
488                hstlist = [str(Histograms[hist]['hId']) for hist in Histograms if 
489                           Histograms[hist]['Instrument Parameters'][0]['Type'][1] == htyp]
490            else:
491                hstlist = [l[1]]
492            if len(l) == 3:
493                for ph in phlist:
494                    for hst in hstlist:
495                        var = ph + ":" + hst + ":" + l[2]
496                        if var in varbs: continue
497                        varbs.append(var)
498            else: # constraints with atoms or rigid bodies
499                if len(l) == 5: # rigid body parameter
500                    var = ':'.join(l)
501                    if var in varbs: continue
502                    varbs.append(var)
503                elif l[3] == "all":
504                    for ph in phlist:
505                        key = G2obj.LookupPhaseName(l[0])[0]
506                        for hst in hstlist: # should be blank
507                            for iatm,at in enumerate(Phases[key]['Atoms']):
508                                var = ph + ":" + hst + ":" + l[2] + ":" + str(iatm)
509                                if var in varbs: continue
510                                varbs.append(var)
511                elif '=' in l[3]:
512                    for ph in phlist:
513                        key = G2obj.LookupPhaseName(l[0])[0]
514                        cx,ct,cs,cia = Phases[key]['General']['AtomPtrs']
515                        for hst in hstlist: # should be blank
516                            atyp = l[3].split('=')[1]
517                            for iatm,at in enumerate(Phases[key]['Atoms']):
518                                if at[ct] != atyp: continue
519                                var = ph + ":" + hst + ":" + l[2] + ":" + str(iatm)
520                                if var in varbs: continue
521                                varbs.append(var)
522                else:
523                    for ph in phlist:
524                        key = G2obj.LookupPhaseName(l[0])[0]
525                        for hst in hstlist: # should be blank
526                            var = ph + ":" + hst + ":" + l[2] + ":" + l[3]
527                            if var in varbs: continue
528                            varbs.append(var)
529        if len(varbs) >= 1 or 'constraint' in constType:
530            constr = [[1.0,FrstVarb]]
531            for item in varbs[1:]:
532                constr += [[1.0,G2obj.G2VarObj(item)]]
533            if 'equivalence' in constType:
534                return [constr+[None,None,'e']]
535            elif 'function' in constType:
536                return [constr+[None,False,'f']]
537            elif 'constraint' in constType:
538                return [constr+[1.0,None,'c']]
539            else:
540                raise Exception,'Unknown constraint type: '+str(constType)
541        else:
542            dlg = wx.MessageDialog(
543                G2frame.dataFrame,
544                'There are no selected variables to include with '+str(FrstVarb),
545                'No variables')
546            dlg.CenterOnParent()
547            dlg.ShowModal()
548            dlg.Destroy()
549        return []
550
551    def CheckAddedConstraint(newcons):
552        '''Check a new constraint that has just been input.
553        If there is an error display a message and give the user a
554        choice to keep or discard the last entry (why keep? -- they
555        may want to delete something else or edit multipliers).
556        Since the varylist is not available, no warning messages
557        should be generated.
558
559        :returns: True if constraint should be added
560        '''
561        allcons = []
562        for key in data:
563            if key.startswith('_'): continue
564            allcons += data[key]
565        allcons += newcons
566        if not len(allcons): return True
567        G2mv.InitVars()   
568        constDictList,fixedList,ignored = G2stIO.ProcessConstraints(allcons)
569        errmsg, warnmsg = G2mv.CheckConstraints('',constDictList,fixedList)
570        if errmsg:
571            res = G2frame.ErrorDialog('Constraint Error',
572                'Error with newly added constraint:\n'+errmsg+
573                '\n\nDiscard newly added constraint?',parent=G2frame.dataFrame,
574                wtype=wx.YES_NO)
575            return res != wx.ID_YES
576        elif warnmsg:
577            print 'Unexpected contraint warning:\n',warnmsg
578        return True
579
580    def CheckChangedConstraint():
581        '''Check all constraints after an edit has been made.
582        If there is an error display a message and give the user a
583        choice to keep or discard the last edit.
584        Since the varylist is not available, no warning messages
585        should be generated.
586       
587        :returns: True if the edit should be retained
588        '''
589        allcons = []
590        for key in data:
591            if key.startswith('_'): continue
592            allcons += data[key]
593        if not len(allcons): return True
594        G2mv.InitVars()   
595        constDictList,fixedList,ignored = G2stIO.ProcessConstraints(allcons)
596        errmsg, warnmsg = G2mv.CheckConstraints('',constDictList,fixedList)
597        if errmsg:
598            res = G2frame.ErrorDialog('Constraint Error',
599                'Error after editing constraint:\n'+errmsg+
600                '\n\nDiscard last constraint edit?',parent=G2frame.dataFrame,
601                wtype=wx.YES_NO)
602            return res != wx.ID_YES
603        elif warnmsg:
604            print 'Unexpected contraint warning:\n',warnmsg
605        return True
606             
607    def PageSelection(page):
608        'Decode page reference'
609        if page[1] == "phs":
610            vartype = "phase"
611            varList = phaseList
612            constrDictEnt = 'Phase'
613        elif page[1] == "hap":
614            vartype = "Histogram*Phase"
615            varList = hapList
616            constrDictEnt = 'HAP'
617        elif page[1] == "hst":
618            vartype = "Histogram"
619            varList = histList
620            constrDictEnt = 'Hist'
621        elif page[1] == "glb":
622            vartype = "Global"
623            varList = globalList
624            constrDictEnt = 'Global'
625        else:
626            raise Exception,'Should not happen!'
627        return vartype,varList,constrDictEnt
628
629    def OnAddHold(event):
630        '''Create a new Hold constraint
631
632        Hold constraints allows the user to select one variable (the list of available
633        variables depends on which tab is currently active).
634        '''
635        page = G2frame.Page
636        vartype,varList,constrDictEnt = PageSelection(page)
637        title1 = "Hold "+vartype+" variable"
638        if not varList:
639            G2frame.ErrorDialog('No variables','There are no variables of type '+vartype,
640                                parent=G2frame.dataFrame)
641            return
642        l2 = l1 = 1
643        for i in varList:
644            l1 = max(l1,len(i))
645            loc,desc = G2obj.VarDescr(i)
646            l2 = max(l2,len(loc))
647        fmt = "{:"+str(l1)+"s} {:"+str(l2)+"s} {:s}"
648        varListlbl = [fmt.format(i,*G2obj.VarDescr(i)) for i in varList]
649        #varListlbl = ["("+i+") "+G2obj.fmtVarDescr(i) for i in varList]
650        legend = "Select variables to hold (Will not be varied, even if vary flag is set)"
651        dlg = G2gd.G2MultiChoiceDialog(
652            G2frame.dataFrame,
653            legend,title1,varListlbl,toggle=False,size=(625,400),monoFont=True)
654        dlg.CenterOnParent()
655        if dlg.ShowModal() == wx.ID_OK:
656            for sel in dlg.GetSelections():
657                Varb = varList[sel]
658                VarObj = G2obj.G2VarObj(Varb)
659                newcons = [[[0.0,VarObj],None,None,'h']]
660                if CheckAddedConstraint(newcons):
661                    data[constrDictEnt] += newcons
662        dlg.Destroy()
663        OnPageChanged(None)
664       
665    def OnAddEquivalence(event):
666        '''add an Equivalence constraint'''
667        page = G2frame.Page
668        vartype,varList,constrDictEnt = PageSelection(page)
669        title1 = "Setup equivalent "+vartype+" variables"
670        title2 = "Select additional "+vartype+" variable(s) to be equivalent with "
671        if not varList:
672            G2frame.ErrorDialog('No variables','There are no variables of type '+vartype,
673                                parent=G2frame.dataFrame)
674            return
675        legend = "Select variables to make equivalent (only one of the variables will be varied when all are set to be varied)"
676        GetAddVars(page,title1,title2,varList,constrDictEnt,'equivalence')
677   
678    def OnAddFunction(event):
679        '''add a Function (new variable) constraint'''
680        page = G2frame.Page
681        vartype,varList,constrDictEnt = PageSelection(page)
682        title1 = "Setup new variable based on "+vartype+" variables"
683        title2 = "Include additional "+vartype+" variable(s) to be included with "
684        if not varList:
685            G2frame.ErrorDialog('No variables','There are no variables of type '+vartype,
686                                parent=G2frame.dataFrame)
687            return
688        legend = "Select variables to include in a new variable (the new variable will be varied when all included variables are varied)"
689        GetAddVars(page,title1,title2,varList,constrDictEnt,'function')
690                       
691    def OnAddConstraint(event):
692        '''add a constraint equation to the constraints list'''
693        page = G2frame.Page
694        vartype,varList,constrDictEnt = PageSelection(page)
695        title1 = "Setup constraint on "+vartype+" variables"
696        title2 = "Select additional "+vartype+" variable(s) to include in constraint with "
697        if not varList:
698            G2frame.ErrorDialog('No variables','There are no variables of type '+vartype,
699                                parent=G2frame.dataFrame)
700            return
701        legend = "Select variables to include in a constraint equation (the values will be constrainted to equal a specified constant)"
702        GetAddVars(page,title1,title2,varList,constrDictEnt,'constraint')
703
704    def GetAddVars(page,title1,title2,varList,constrDictEnt,constType):
705        '''Get the variables to be added for OnAddEquivalence, OnAddFunction,
706        and OnAddConstraint. Then create and check the constraint.
707        '''
708        #varListlbl = ["("+i+") "+G2obj.fmtVarDescr(i) for i in varList]
709        l2 = l1 = 1
710        for i in varList:
711            l1 = max(l1,len(i))
712            loc,desc = G2obj.VarDescr(i)
713            l2 = max(l2,len(loc))
714        fmt = "{:"+str(l1)+"s} {:"+str(l2)+"s} {:s}"
715        varListlbl = [fmt.format(i,*G2obj.VarDescr(i)) for i in varList]       
716        dlg = G2gd.G2SingleChoiceDialog(G2frame.dataFrame,'Select 1st variable:',
717                                      title1,varListlbl,
718                                      monoFont=True,size=(625,400))
719        dlg.CenterOnParent()
720        if dlg.ShowModal() == wx.ID_OK:
721            sel = dlg.GetSelection()
722            FrstVarb = varList[sel]
723            VarObj = G2obj.G2VarObj(FrstVarb)
724            moreVarb = FindEquivVarb(FrstVarb,varList)
725            newcons = SelectVarbs(page,VarObj,moreVarb,title2+FrstVarb,constType)
726            if len(newcons) > 0:
727                if CheckAddedConstraint(newcons):
728                    data[constrDictEnt] += newcons
729        dlg.Destroy()
730        OnPageChanged(None)
731                       
732    def MakeConstraintsSizer(name,pageDisplay):
733        '''Creates a sizer displaying all of the constraints entered of
734        the specified type.
735
736        :param str name: the type of constraints to be displayed ('HAP',
737          'Hist', 'Phase', or 'Global')
738        :param wx.Panel pageDisplay: parent panel for sizer
739        :returns: wx.Sizer created by method
740        '''
741        constSizer = wx.FlexGridSizer(0,6,0,0)
742        maxlen = 70 # characters before wrapping a constraint
743        for Id,item in enumerate(data[name]):
744            refineflag = False
745            helptext = ""
746            eqString = ['',]
747            if item[-1] == 'h': # Hold on variable
748                constSizer.Add((-1,-1),0)              # blank space for edit button
749                typeString = 'FIXED'
750                var = str(item[0][1])
751                varMean = G2obj.fmtVarDescr(var)
752                eqString[-1] =  var +'   '
753                helptext = "Prevents variable:\n"+ var + " ("+ varMean + ")\nfrom being changed"
754            elif isinstance(item[-1],str): # not true on original-style (2011?) constraints
755                constEdit = wx.Button(pageDisplay,-1,'Edit',style=wx.BU_EXACTFIT)
756                constEdit.Bind(wx.EVT_BUTTON,OnConstEdit)
757                constSizer.Add(constEdit,0,wx.LEFT|wx.RIGHT|wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER,1)            # edit button
758                Indx[constEdit.GetId()] = [Id,name]
759                if item[-1] == 'f':
760                    helptext = "A new variable"
761                    if item[-3]:
762                        helptext += " named "+str(item[-3])
763                    helptext += " is created from a linear combination of the following variables:\n"
764                    for term in item[:-3]:
765                        var = str(term[1])
766                        if len(eqString[-1]) > maxlen:
767                            eqString.append(' ')
768                        m = term[0]
769                        if eqString[-1] != '':
770                            if m >= 0:
771                                eqString[-1] += ' + '
772                            else:
773                                eqString[-1] += ' - '
774                                m = abs(m)
775                        eqString[-1] += '%.3f*%s '%(m,var)
776                        varMean = G2obj.fmtVarDescr(var)
777                        helptext += "\n" + var + " ("+ varMean + ")"
778                    if '_Explain' in data:
779                        if data['_Explain'].get(item[-3]):
780                            helptext += '\n\n'
781                            helptext += data['_Explain'][item[-3]]
782                    # typeString = 'NEWVAR'
783                    # if item[-3]:
784                    #     eqString[-1] += ' = '+item[-3]
785                    # else:
786                    #     eqString[-1] += ' = New Variable'
787                    if item[-3]:
788                        typeString = item[-3] + ' = '
789                    else:
790                        typeString = 'New Variable = '
791                    #print 'refine',item[-2]
792                    refineflag = True
793                elif item[-1] == 'c':
794                    helptext = "The following variables constrained to equal a constant:"
795                    for term in item[:-3]:
796                        var = str(term[1])
797                        if len(eqString[-1]) > maxlen:
798                            eqString.append(' ')
799                        if eqString[-1] != '':
800                            if term[0] > 0:
801                                eqString[-1] += ' + '
802                            else:
803                                eqString[-1] += ' - '
804                        eqString[-1] += '%.3f*%s '%(abs(term[0]),var)
805                        varMean = G2obj.fmtVarDescr(var)
806                        helptext += "\n" + var + " ("+ varMean + ")"
807                    typeString = 'CONST'
808                    eqString[-1] += ' = '+str(item[-3])
809                elif item[-1] == 'e':
810                    helptext = "The following variables are set to be equivalent, noting multipliers:"
811                    for term in item[:-3]:
812                        var = str(term[1])
813                        if term[0] == 0: term[0] = 1.0
814                        if len(eqString[-1]) > maxlen:
815                            eqString.append(' ')
816                        if eqString[-1] == '':
817                            eqString[-1] += var+' '
818                            first = term[0]
819                        else:
820                            eqString[-1] += ' = %.3f*%s '%(first/term[0],var)
821                        varMean = G2obj.fmtVarDescr(var)
822                        helptext += "\n" + var + " ("+ varMean + ")"
823                    typeString = 'EQUIV'
824                else:
825                    print 'Unexpected constraint',item
826               
827            else:
828                print 'Removing old-style constraints'
829                data[name] = []
830                return constSizer
831            constDel = wx.Button(pageDisplay,-1,'Delete',style=wx.BU_EXACTFIT)
832            constDel.Bind(wx.EVT_BUTTON,OnConstDel)
833            Indx[constDel.GetId()] = [Id,name]
834            constSizer.Add(constDel,0,wx.LEFT|wx.RIGHT|wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER,1)             # delete button
835            if helptext:
836                ch = G2gd.HelpButton(pageDisplay,helptext)
837                constSizer.Add(ch,0,wx.LEFT|wx.RIGHT|wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER,1)
838            else:
839                constSizer.Add((-1,-1))
840            if refineflag:
841                ch = G2gd.G2CheckBox(pageDisplay,'',item,-2)
842                constSizer.Add(ch,0,wx.LEFT|wx.RIGHT|wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER,1)
843            else:
844                constSizer.Add((-1,-1))               
845            constSizer.Add(wx.StaticText(pageDisplay,-1,typeString),
846                           0,wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER|wx.RIGHT|wx.LEFT,3)
847            if len(eqString) > 1:
848                Eq = wx.BoxSizer(wx.VERTICAL)
849                for s in eqString:
850                    Eq.Add(wx.StaticText(pageDisplay,-1,s),0,wx.ALIGN_CENTER_VERTICAL)
851            else:
852                Eq = wx.StaticText(pageDisplay,-1,eqString[0])
853            constSizer.Add(Eq,1,wx.ALIGN_CENTER_VERTICAL)
854        return constSizer
855               
856    def OnConstDel(event):
857        'Delete a constraint'
858        Obj = event.GetEventObject()
859        Id,name = Indx[Obj.GetId()]
860        del(data[name][Id])
861        OnPageChanged(None)       
862       
863    def OnConstEdit(event):
864        '''Called to edit an individual contraint in response to a
865        click on its Edit button
866        '''
867        Obj = event.GetEventObject()
868        Id,name = Indx[Obj.GetId()]
869        if data[name][Id][-1] == 'f':
870            items = data[name][Id][:-3]
871            constType = 'New Variable'
872            if data[name][Id][-3]:
873                varname = data[name][Id][-3]
874            else:
875                varname = ""
876            lbl = 'Enter value for each term in constraint; sum = new variable'
877            dlg = ConstraintDialog(G2frame.dataFrame,constType,lbl,items,
878                                   varname=varname,varyflag=data[name][Id][-2])
879        elif data[name][Id][-1] == 'c':
880            items = data[name][Id][:-3]+[
881                [data[name][Id][-3],'fixed value =']]
882            constType = 'Constraint'
883            lbl = 'Edit value for each term in constant constraint sum'
884            dlg = ConstraintDialog(G2frame.dataFrame,constType,lbl,items)
885        elif data[name][Id][-1] == 'e':
886            items = data[name][Id][:-3]
887            constType = 'Equivalence'
888            lbl = 'The following terms are set to be equal:'
889            dlg = ConstraintDialog(G2frame.dataFrame,constType,lbl,items,'/')
890        else:
891            return
892        try:
893            prev = data[name][Id][:]
894            if dlg.ShowModal() == wx.ID_OK:
895                result = dlg.GetData()
896                for i in range(len(data[name][Id][:-3])):
897                    if type(data[name][Id][i]) is tuple: # fix non-mutable construct
898                        data[name][Id][i] = list(data[name][Id][i])
899                    data[name][Id][i][0] = result[i][0]
900                if data[name][Id][-1] == 'c':
901                    data[name][Id][-3] = str(result[-1][0])
902                elif data[name][Id][-1] == 'f':
903                    # process the variable name to put in global form (::var)
904                    varname = str(dlg.newvar[0]).strip().replace(' ','_')
905                    if varname.startswith('::'):
906                        varname = varname[2:]
907                    varname = varname.replace(':',';')
908                    if varname:
909                        data[name][Id][-3] = varname
910                    else:
911                        data[name][Id][-3] = ''
912                    data[name][Id][-2] = dlg.newvar[1]
913                if not CheckChangedConstraint():
914                    data[name][Id] = prev
915        except:
916            import traceback
917            print traceback.format_exc()
918        finally:
919            dlg.Destroy()           
920        OnPageChanged(None)                     
921   
922    def UpdateConstraintPanel(panel,typ):
923        '''Update the contents of the selected Constraint
924        notebook tab. Called in :func:`OnPageChanged`
925        '''
926        if panel.GetSizer(): panel.GetSizer().Clear(True) # N.B. don't use panel.DestroyChildren()
927        # because it deletes scrollbars on Mac
928        Siz = wx.BoxSizer(wx.VERTICAL)
929        Siz.Add((5,5),0)
930        Siz.Add(MakeConstraintsSizer(typ,panel))
931        panel.SetSizer(Siz,True)
932        Size = Siz.GetMinSize()
933        Size[0] += 40
934        Size[1] = max(Size[1],450) + 20
935        panel.SetSize(Size)
936        panel.SetScrollbars(10,10,Size[0]/10-4,Size[1]/10-1)
937        Size[1] = min(500,Size[1])
938        G2frame.dataFrame.setSizePosLeft(Size)
939#        G2frame.dataFrame.SetSize((500,250)) # set frame size here
940
941    def OnPageChanged(event):
942        '''Called when a tab is pressed or when a "select tab" menu button is
943        used (see RaisePage), or to refresh the current tab contents (event=None)
944        '''
945        if event:       #page change event!
946            page = event.GetSelection()
947        else: # called directly, get current page
948            page = G2frame.dataDisplay.GetSelection()
949        oldPage = G2frame.dataDisplay.ChangeSelection(page)
950        text = G2frame.dataDisplay.GetPageText(page)
951        if text == 'Histogram/Phase constraints':
952            G2frame.Page = [page,'hap']
953            UpdateConstraintPanel(HAPConstr,'HAP')
954        elif text == 'Histogram constraints':
955            G2frame.Page = [page,'hst']
956            UpdateConstraintPanel(HistConstr,'Hist')
957        elif text == 'Phase constraints':
958            G2frame.Page = [page,'phs']
959            UpdateConstraintPanel(PhaseConstr,'Phase')
960        elif text == 'Global constraints':
961            G2frame.Page = [page,'glb']
962            UpdateConstraintPanel(GlobalConstr,'Global')
963
964    def RaisePage(event):
965        'Respond to a "select tab" menu button'
966        try:
967            i = (G2gd.wxID_CONSPHASE,
968                 G2gd.wxID_CONSHAP,
969                 G2gd.wxID_CONSHIST,
970                 G2gd.wxID_CONSGLOBAL).index(event.GetId())
971            G2frame.dataDisplay.SetSelection(i)
972            OnPageChanged(None)
973        except ValueError:
974            print('Unexpected event in RaisePage')
975
976    def SetStatusLine(text):
977        Status.SetStatusText(text)                                     
978       
979    if G2frame.dataDisplay:
980        G2frame.dataDisplay.Destroy()
981    G2gd.SetDataMenuBar(G2frame,G2frame.dataFrame.ConstraintMenu)
982    G2frame.dataFrame.SetLabel('Constraints')
983    if not G2frame.dataFrame.GetStatusBar():
984        Status = G2frame.dataFrame.CreateStatusBar()
985    SetStatusLine('')
986   
987    G2gd.SetDataMenuBar(G2frame,G2frame.dataFrame.ConstraintMenu)
988    G2frame.dataFrame.Bind(wx.EVT_MENU, OnAddConstraint, id=G2gd.wxID_CONSTRAINTADD)
989    G2frame.dataFrame.Bind(wx.EVT_MENU, OnAddFunction, id=G2gd.wxID_FUNCTADD)
990    G2frame.dataFrame.Bind(wx.EVT_MENU, OnAddEquivalence, id=G2gd.wxID_EQUIVADD)
991    G2frame.dataFrame.Bind(wx.EVT_MENU, OnAddHold, id=G2gd.wxID_HOLDADD)
992    # tab commands
993    for id in (G2gd.wxID_CONSPHASE,
994               G2gd.wxID_CONSHAP,
995               G2gd.wxID_CONSHIST,
996               G2gd.wxID_CONSGLOBAL):
997        G2frame.dataFrame.Bind(wx.EVT_MENU, RaisePage,id=id)
998
999    G2frame.dataDisplay = G2gd.GSNoteBook(parent=G2frame.dataFrame)
1000    # note that order of pages is hard-coded in RaisePage
1001    PhaseConstr = wx.ScrolledWindow(G2frame.dataDisplay)
1002    G2frame.dataDisplay.AddPage(PhaseConstr,'Phase constraints')
1003    HAPConstr = wx.ScrolledWindow(G2frame.dataDisplay)
1004    G2frame.dataDisplay.AddPage(HAPConstr,'Histogram/Phase constraints')
1005    HistConstr = wx.ScrolledWindow(G2frame.dataDisplay)
1006    G2frame.dataDisplay.AddPage(HistConstr,'Histogram constraints')
1007    GlobalConstr = wx.ScrolledWindow(G2frame.dataDisplay)
1008    G2frame.dataDisplay.AddPage(GlobalConstr,'Global constraints')
1009    OnPageChanged(None) # show initial page
1010    G2frame.dataDisplay.Bind(wx.aui.EVT_AUINOTEBOOK_PAGE_CHANGED, OnPageChanged)
1011    # validate all the constrants -- should not see any errors here normally
1012    allcons = []
1013    for key in data:
1014        if key.startswith('_'): continue
1015        allcons += data[key]
1016    if not len(allcons): return
1017    G2mv.InitVars()   
1018    constDictList,fixedList,ignored = G2stIO.ProcessConstraints(allcons)
1019    errmsg, warnmsg = G2mv.CheckConstraints('',constDictList,fixedList)
1020    if errmsg:
1021        G2frame.ErrorDialog('Constraint Error','Error in constraints:\n'+errmsg,
1022            parent=G2frame.dataFrame)
1023    elif warnmsg:
1024        print 'Unexpected contraint warning:\n',warnmsg
1025       
1026################################################################################
1027#### Rigid bodies
1028################################################################################
1029
1030def UpdateRigidBodies(G2frame,data):
1031    '''Called when Rigid bodies tree item is selected.
1032    Displays the rigid bodies in the data window
1033    '''
1034    if not data.get('RBIds') or not data:
1035        data.update({'Vector':{'AtInfo':{}},'Residue':{'AtInfo':{}},
1036            'RBIds':{'Vector':[],'Residue':[]}})       #empty/bad dict - fill it
1037           
1038    global resList
1039    Indx = {}
1040    resList = []
1041    plotDefaults = {'oldxy':[0.,0.],'Quaternion':[0.,0.,0.,1.],'cameraPos':30.,'viewDir':[0,0,1],}
1042   
1043    def OnPageChanged(event):
1044        global resList
1045        resList = []
1046        if event:       #page change event!
1047            page = event.GetSelection()
1048        else:
1049            page = G2frame.dataDisplay.GetSelection()
1050        oldPage = G2frame.dataDisplay.ChangeSelection(page)
1051        text = G2frame.dataDisplay.GetPageText(page)
1052        if text == 'Vector rigid bodies':
1053            G2gd.SetDataMenuBar(G2frame,G2frame.dataFrame.VectorBodyMenu)
1054            G2frame.dataFrame.Bind(wx.EVT_MENU, AddVectorRB, id=G2gd.wxID_VECTORBODYADD)
1055            G2frame.Page = [page,'vrb']
1056            UpdateVectorRB()
1057        elif text == 'Residue rigid bodies':
1058            G2gd.SetDataMenuBar(G2frame,G2frame.dataFrame.RigidBodyMenu)
1059            G2frame.dataFrame.Bind(wx.EVT_MENU, AddResidueRB, id=G2gd.wxID_RIGIDBODYADD)
1060            G2frame.dataFrame.Bind(wx.EVT_MENU, OnImportRigidBody, id=G2gd.wxID_RIGIDBODYIMPORT)
1061            G2frame.dataFrame.Bind(wx.EVT_MENU, OnDefineTorsSeq, id=G2gd.wxID_RESIDUETORSSEQ)
1062            G2frame.Page = [page,'rrb']
1063            UpdateResidueRB()
1064           
1065    def getMacroFile(macName):
1066        defDir = os.path.join(os.path.split(__file__)[0],'GSASIImacros')
1067        dlg = wx.FileDialog(G2frame,message='Choose '+macName+' rigid body macro file',
1068            defaultDir=defDir,defaultFile="",wildcard="GSAS-II macro file (*.mac)|*.mac",
1069            style=wx.OPEN | wx.CHANGE_DIR)
1070        try:
1071            if dlg.ShowModal() == wx.ID_OK:
1072                macfile = dlg.GetPath()
1073                macro = open(macfile,'Ur')
1074                head = macro.readline()
1075                if macName not in head:
1076                    print head
1077                    print '**** ERROR - wrong restraint macro file selected, try again ****'
1078                    macro = []
1079            else: # cancel was pressed
1080                macro = []
1081        finally:
1082            dlg.Destroy()
1083        return macro        #advanced past 1st line
1084       
1085    def getTextFile():
1086        defDir = os.path.join(os.path.split(__file__)[0],'GSASIImacros')
1087        dlg = wx.FileDialog(G2frame,'Choose rigid body text file', '.', '',
1088            "GSAS-II text file (*.txt)|*.txt|XYZ file (*.xyz)|*.xyz|"
1089            "Sybyl mol2 file (*.mol2)|*.mol2|PDB file (*.pdb;*.ent)|*.pdb;*.ent",
1090            wx.OPEN | wx.CHANGE_DIR)
1091        try:
1092            if dlg.ShowModal() == wx.ID_OK:
1093                txtfile = dlg.GetPath()
1094                ext = os.path.splitext(txtfile)[1]
1095                text = open(txtfile,'Ur')
1096            else: # cancel was pressed
1097                ext = ''
1098                text = []
1099        finally:
1100            dlg.Destroy()
1101        if 'ent' in ext:
1102            ext = '.pdb'
1103        return text,ext.lower()
1104       
1105    def OnImportRigidBody(event):
1106        page = G2frame.dataDisplay.GetSelection()
1107        if 'Vector' in G2frame.dataDisplay.GetPageText(page):
1108            pass
1109        elif 'Residue' in G2frame.dataDisplay.GetPageText(page):
1110            ImportResidueRB()
1111           
1112    def AddVectorRB(event):
1113        AtInfo = data['Vector']['AtInfo']
1114        dlg = MultiIntegerDialog(G2frame.dataDisplay,'New Rigid Body',['No. atoms','No. translations'],[1,1])
1115        if dlg.ShowModal() == wx.ID_OK:
1116            nAtoms,nTrans = dlg.GetValues()
1117            vectorRB = data['Vector']
1118            rbId = ran.randint(0,sys.maxint)
1119            vecMag = [1.0 for i in range(nTrans)]
1120            vecRef = [False for i in range(nTrans)]
1121            vecVal = [np.zeros((nAtoms,3)) for j in range(nTrans)]
1122            rbTypes = ['C' for i in range(nAtoms)]
1123            Info = G2elem.GetAtomInfo('C')
1124            AtInfo['C'] = [Info['Drad'],Info['Color']]
1125            data['Vector'][rbId] = {'RBname':'UNKRB','VectMag':vecMag,'rbXYZ':np.zeros((nAtoms,3)),
1126                'rbRef':[0,1,2,False],'VectRef':vecRef,'rbTypes':rbTypes,'rbVect':vecVal,'useCount':0}
1127            data['RBIds']['Vector'].append(rbId)
1128        dlg.Destroy()
1129        UpdateVectorRB()
1130       
1131    def AddResidueRB(event):
1132        AtInfo = data['Residue']['AtInfo']
1133        macro = getMacroFile('rigid body')
1134        if not macro:
1135            return
1136        macStr = macro.readline()
1137        while macStr:
1138            items = macStr.split()
1139            if 'I' == items[0]:
1140                rbId = ran.randint(0,sys.maxint)
1141                rbName = items[1]
1142                rbTypes = []
1143                rbXYZ = []
1144                rbSeq = []
1145                atNames = []
1146                nAtms,nSeq,nOrig,mRef,nRef = [int(items[i]) for i in [2,3,4,5,6]]
1147                for iAtm in range(nAtms):
1148                    macStr = macro.readline().split()
1149                    atName = macStr[0]
1150                    atType = macStr[1]
1151                    atNames.append(atName)
1152                    rbXYZ.append([float(macStr[i]) for i in [2,3,4]])
1153                    rbTypes.append(atType)
1154                    if atType not in AtInfo:
1155                        Info = G2elem.GetAtomInfo(atType)
1156                        AtInfo[atType] = [Info['Drad'],Info['Color']]
1157                rbXYZ = np.array(rbXYZ)-np.array(rbXYZ[nOrig-1])
1158                for iSeq in range(nSeq):
1159                    macStr = macro.readline().split()
1160                    mSeq = int(macStr[0])
1161                    for jSeq in range(mSeq):
1162                        macStr = macro.readline().split()
1163                        iBeg = int(macStr[0])-1
1164                        iFin = int(macStr[1])-1
1165                        angle = 0.0
1166                        nMove = int(macStr[2])
1167                        iMove = [int(macStr[i])-1 for i in range(3,nMove+3)]
1168                        rbSeq.append([iBeg,iFin,angle,iMove])
1169                data['Residue'][rbId] = {'RBname':rbName,'rbXYZ':rbXYZ,'rbTypes':rbTypes,
1170                    'atNames':atNames,'rbRef':[nOrig-1,mRef-1,nRef-1,True],'rbSeq':rbSeq,
1171                    'SelSeq':[0,0],'useCount':0}
1172                data['RBIds']['Residue'].append(rbId)
1173                print 'Rigid body '+rbName+' added'
1174            macStr = macro.readline()
1175        macro.close()
1176        UpdateResidueRB()
1177       
1178    def ImportResidueRB():
1179        AtInfo = data['Residue']['AtInfo']
1180        text,ext = getTextFile()
1181        if not text:
1182            return
1183        rbId = ran.randint(0,sys.maxint)
1184        rbTypes = []
1185        rbXYZ = []
1186        rbSeq = []
1187        atNames = []
1188        txtStr = text.readline()
1189        if 'xyz' in ext:
1190            txtStr = text.readline()
1191            txtStr = text.readline()
1192        elif 'mol2' in ext:
1193            while 'ATOM' not in txtStr:
1194                txtStr = text.readline()
1195            txtStr = text.readline()
1196        elif 'pdb' in ext:
1197            while 'ATOM' not in txtStr[:6] and 'HETATM' not in txtStr[:6]:
1198                txtStr = text.readline()
1199                #print txtStr
1200        items = txtStr.split()
1201        while len(items):
1202            if 'txt' in ext:
1203                atName = items[0]
1204                atType = items[1]
1205                rbXYZ.append([float(items[i]) for i in [2,3,4]])
1206            elif 'xyz' in ext:
1207                atType = items[0]
1208                rbXYZ.append([float(items[i]) for i in [1,2,3]])
1209                atName = atType+str(len(rbXYZ))
1210            elif 'mol2' in ext:
1211                atType = items[1]
1212                atName = items[1]+items[0]
1213                rbXYZ.append([float(items[i]) for i in [2,3,4]])
1214            elif 'pdb' in ext:
1215                atType = items[-1]
1216                atName = items[2]
1217                xyz = txtStr[30:55].split()                   
1218                rbXYZ.append([float(x) for x in xyz])
1219            atNames.append(atName)
1220            rbTypes.append(atType)
1221            if atType not in AtInfo:
1222                Info = G2elem.GetAtomInfo(atType)
1223                AtInfo[atType] = [Info['Drad'],Info['Color']]
1224            txtStr = text.readline()
1225            if 'mol2' in ext and 'BOND' in txtStr:
1226                break
1227            if 'pdb' in ext and ('ATOM' not in txtStr[:6] and 'HETATM' not in txtStr[:6]):
1228                break
1229            items = txtStr.split()
1230        rbXYZ = np.array(rbXYZ)-np.array(rbXYZ[0])
1231        data['Residue'][rbId] = {'RBname':'UNKRB','rbXYZ':rbXYZ,'rbTypes':rbTypes,
1232            'atNames':atNames,'rbRef':[0,1,2,False],'rbSeq':[],'SelSeq':[0,0],'useCount':0}
1233        data['RBIds']['Residue'].append(rbId)
1234        print 'Rigid body UNKRB added'
1235        text.close()
1236        UpdateResidueRB()
1237       
1238    def FindNeighbors(Orig,XYZ,atTypes,atNames,AtInfo):
1239        Radii = []
1240        for Atype in atTypes:
1241            Radii.append(AtInfo[Atype][0])
1242        Radii = np.array(Radii)
1243        Neigh = []
1244        Dx = XYZ-XYZ[Orig]
1245        dist = np.sqrt(np.sum(Dx**2,axis=1))
1246        sumR = Radii[Orig]+Radii
1247        IndB = ma.nonzero(ma.masked_greater(dist-0.85*sumR,0.))
1248        for j in IndB[0]:
1249            if j != Orig:
1250                Neigh.append(atNames[j])
1251        return Neigh
1252       
1253    def FindAllNeighbors(XYZ,atTypes,atNames,AtInfo):
1254        NeighDict = {}
1255        for iat,xyz in enumerate(atNames):
1256            NeighDict[atNames[iat]] = FindNeighbors(iat,XYZ,atTypes,atNames,AtInfo)
1257        return NeighDict
1258       
1259    def FindRiding(Orig,Pivot,NeighDict):
1260        riding = [Orig,Pivot]
1261        iAdd = 1
1262        new = True
1263        while new:
1264            newAtms = NeighDict[riding[iAdd]]
1265            for At in newAtms:
1266                new = False
1267                if At not in riding:
1268                    riding.append(At)
1269                    new = True
1270            iAdd += 1
1271            if iAdd < len(riding):
1272                new = True
1273        return riding[2:]
1274                       
1275    def OnDefineTorsSeq(event):
1276        rbKeys = data['Residue'].keys()
1277        rbKeys.remove('AtInfo')
1278        rbNames = [data['Residue'][k]['RBname'] for k in rbKeys]
1279        rbIds = dict(zip(rbNames,rbKeys))
1280        rbNames.sort()
1281        rbId = 0
1282        if len(rbNames) > 1:
1283            dlg = wx.SingleChoiceDialog(G2frame,'Select rigid body for torsion sequence','Torsion sequence',rbNames)
1284            if dlg.ShowModal() == wx.ID_OK:
1285                sel = dlg.GetSelection()
1286                rbId = rbIds[rbNames[sel]]
1287                rbData = data['Residue'][rbId]
1288            dlg.Destroy()
1289        else:
1290            rbId = rbIds[rbNames[0]]
1291            rbData = data['Residue'][rbId]
1292        if not len(rbData):
1293            return
1294        atNames = rbData['atNames']
1295        AtInfo = data['Residue']['AtInfo']
1296        atTypes = rbData['rbTypes']
1297        XYZ = rbData['rbXYZ']
1298        neighDict = FindAllNeighbors(XYZ,atTypes,atNames,AtInfo)
1299        TargList = []           
1300        dlg = wx.SingleChoiceDialog(G2frame,'Select origin atom for torsion sequence','Origin atom',rbData['atNames'])
1301        if dlg.ShowModal() == wx.ID_OK:
1302            Orig = dlg.GetSelection()
1303            xyz = XYZ[Orig]
1304            TargList = neighDict[atNames[Orig]]
1305        dlg.Destroy()
1306        if not len(TargList):
1307            return
1308        dlg = wx.SingleChoiceDialog(G2frame,'Select pivot atom for torsion sequence','Pivot atom',TargList)
1309        if dlg.ShowModal() == wx.ID_OK:
1310            Piv = atNames.index(TargList[dlg.GetSelection()])
1311            riding = FindRiding(atNames[Orig],atNames[Piv],neighDict)
1312            Riding = []
1313            for atm in riding:
1314                Riding.append(atNames.index(atm))
1315            rbData['rbSeq'].append([Orig,Piv,0.0,Riding])           
1316        dlg.Destroy()
1317        UpdateResidueRB()
1318
1319    def UpdateVectorRB(Scroll=0):
1320        AtInfo = data['Vector']['AtInfo']
1321        refChoice = {}
1322        SetStatusLine(' You may use e.g. "c60" or "s60" for a vector entry')
1323        def rbNameSizer(rbId,rbData):
1324
1325            def OnRBName(event):
1326                Obj = event.GetEventObject()
1327                rbId = Indx[Obj.GetId()]
1328                rbData['RBname'] = Obj.GetValue()
1329               
1330            def OnDelRB(event):
1331                Obj = event.GetEventObject()
1332                rbId = Indx[Obj.GetId()]
1333                del data['Vector'][rbId]
1334                data['RBIds']['Vector'].remove(rbId)
1335                rbData['useCount'] -= 1
1336                wx.CallAfter(UpdateVectorRB)
1337               
1338            def OnPlotRB(event):
1339                Obj = event.GetEventObject()
1340                rbId = Indx[Obj.GetId()]
1341                Obj.SetValue(False)
1342                G2plt.PlotRigidBody(G2frame,'Vector',AtInfo,rbData,plotDefaults)
1343           
1344            nameSizer = wx.BoxSizer(wx.HORIZONTAL)
1345            nameSizer.Add(wx.StaticText(VectorRBDisplay,-1,'Rigid body name: '),
1346                0,wx.ALIGN_CENTER_VERTICAL)
1347            RBname = wx.TextCtrl(VectorRBDisplay,-1,rbData['RBname'])
1348            Indx[RBname.GetId()] = rbId
1349            RBname.Bind(wx.EVT_TEXT_ENTER,OnRBName)
1350            RBname.Bind(wx.EVT_KILL_FOCUS,OnRBName)
1351            nameSizer.Add(RBname,0,wx.ALIGN_CENTER_VERTICAL)
1352            nameSizer.Add((5,0),)
1353            plotRB = wx.CheckBox(VectorRBDisplay,-1,'Plot?')
1354            Indx[plotRB.GetId()] = rbId
1355            plotRB.Bind(wx.EVT_CHECKBOX,OnPlotRB)
1356            nameSizer.Add(plotRB,0,wx.ALIGN_CENTER_VERTICAL)
1357            nameSizer.Add((5,0),)
1358            if not rbData['useCount']:
1359                delRB = wx.CheckBox(VectorRBDisplay,-1,'Delete?')
1360                Indx[delRB.GetId()] = rbId
1361                delRB.Bind(wx.EVT_CHECKBOX,OnDelRB)
1362                nameSizer.Add(delRB,0,wx.ALIGN_CENTER_VERTICAL)
1363            return nameSizer
1364           
1365        def rbRefAtmSizer(rbId,rbData):
1366           
1367            def OnRefSel(event):
1368                Obj = event.GetEventObject()
1369                iref = Indx[Obj.GetId()]
1370                sel = Obj.GetValue()
1371                rbData['rbRef'][iref] = atNames.index(sel)
1372                FillRefChoice(rbId,rbData)
1373           
1374            refAtmSizer = wx.BoxSizer(wx.HORIZONTAL)
1375            atNames = [name+str(i) for i,name in enumerate(rbData['rbTypes'])]
1376            rbRef = rbData.get('rbRef',[0,1,2,False])
1377            rbData['rbRef'] = rbRef
1378            if rbData['useCount']:
1379                refAtmSizer.Add(wx.StaticText(VectorRBDisplay,-1,
1380                    'Orientation reference atoms A-B-C: %s, %s, %s'%(atNames[rbRef[0]], \
1381                     atNames[rbRef[1]],atNames[rbRef[2]])),0)
1382            else:
1383                refAtmSizer.Add(wx.StaticText(VectorRBDisplay,-1,
1384                    'Orientation reference atoms A-B-C: '),0,wx.ALIGN_CENTER_VERTICAL)
1385                for i in range(3):
1386                    choices = [atNames[j] for j in refChoice[rbId][i]]
1387                    refSel = wx.ComboBox(VectorRBDisplay,-1,value='',
1388                        choices=choices,style=wx.CB_READONLY|wx.CB_DROPDOWN)
1389                    refSel.SetValue(atNames[rbRef[i]])
1390                    refSel.Bind(wx.EVT_COMBOBOX, OnRefSel)
1391                    Indx[refSel.GetId()] = i
1392                    refAtmSizer.Add(refSel,0,wx.ALIGN_CENTER_VERTICAL)
1393            return refAtmSizer
1394                       
1395        def rbVectMag(rbId,imag,rbData):
1396           
1397            def OnRBVectorMag(event):
1398                Obj = event.GetEventObject()
1399                rbId,imag = Indx[Obj.GetId()]
1400                try:
1401                    val = float(Obj.GetValue())
1402                    if val <= 0.:
1403                        raise ValueError
1404                    rbData['VectMag'][imag] = val
1405                except ValueError:
1406                    pass
1407                Obj.SetValue('%8.4f'%(val))
1408                wx.CallAfter(UpdateVectorRB,VectorRB.GetScrollPos(wx.VERTICAL))
1409                G2plt.PlotRigidBody(G2frame,'Vector',AtInfo,data['Vector'][rbId],plotDefaults)
1410               
1411            def OnRBVectorRef(event):
1412                Obj = event.GetEventObject()
1413                rbId,imag = Indx[Obj.GetId()]
1414                rbData['VectRef'][imag] = Obj.GetValue()
1415                       
1416            magSizer = wx.wx.BoxSizer(wx.HORIZONTAL)
1417            magSizer.Add(wx.StaticText(VectorRBDisplay,-1,'Translation magnitude: '),
1418                0,wx.ALIGN_CENTER_VERTICAL)
1419            magValue = wx.TextCtrl(VectorRBDisplay,-1,'%8.4f'%(rbData['VectMag'][imag]))
1420            Indx[magValue.GetId()] = [rbId,imag]
1421            magValue.Bind(wx.EVT_TEXT_ENTER,OnRBVectorMag)
1422            magValue.Bind(wx.EVT_KILL_FOCUS,OnRBVectorMag)
1423            magSizer.Add(magValue,0,wx.ALIGN_CENTER_VERTICAL)
1424            magSizer.Add((5,0),)
1425            magref = wx.CheckBox(VectorRBDisplay,-1,label=' Refine?') 
1426            magref.SetValue(rbData['VectRef'][imag])
1427            magref.Bind(wx.EVT_CHECKBOX,OnRBVectorRef)
1428            Indx[magref.GetId()] = [rbId,imag]
1429            magSizer.Add(magref,0,wx.ALIGN_CENTER_VERTICAL)
1430            return magSizer
1431           
1432        def rbVectors(rbId,imag,mag,XYZ,rbData):
1433
1434            def TypeSelect(event):
1435                Obj = event.GetEventObject()
1436                AtInfo = data['Vector']['AtInfo']
1437                r,c = event.GetRow(),event.GetCol()
1438                if vecGrid.GetColLabelValue(c) == 'Type':
1439                    PE = G2elemGUI.PickElement(G2frame,oneOnly=True)
1440                    if PE.ShowModal() == wx.ID_OK:
1441                        if PE.Elem != 'None':
1442                            El = PE.Elem.strip().lower().capitalize()
1443                            if El not in AtInfo:
1444                                Info = G2elem.GetAtomInfo(El)
1445                                AtInfo[El] = [Info['Drad'],Info['Color']]
1446                            rbData['rbTypes'][r] = El
1447                            vecGrid.SetCellValue(r,c,El)
1448                    PE.Destroy()
1449                wx.CallAfter(UpdateVectorRB,VectorRB.GetScrollPos(wx.VERTICAL))
1450
1451            def ChangeCell(event):
1452                Obj = event.GetEventObject()
1453                r,c =  event.GetRow(),event.GetCol()
1454                if r >= 0 and (0 <= c < 3):
1455                    try:
1456                        val = float(vecGrid.GetCellValue(r,c))
1457                        rbData['rbVect'][imag][r][c] = val
1458                    except ValueError:
1459                        pass
1460                G2plt.PlotRigidBody(G2frame,'Vector',AtInfo,data['Vector'][rbId],plotDefaults)
1461                wx.CallAfter(UpdateVectorRB,VectorRB.GetScrollPos(wx.VERTICAL))
1462
1463            vecSizer = wx.BoxSizer()
1464            Types = 3*[wg.GRID_VALUE_FLOAT+':10,5',]+[wg.GRID_VALUE_STRING,]+3*[wg.GRID_VALUE_FLOAT+':10,5',]
1465            colLabels = ['Vector x','Vector y','Vector z','Type','Cart x','Cart y','Cart z']
1466            table = []
1467            rowLabels = []
1468            for ivec,xyz in enumerate(rbData['rbVect'][imag]):
1469                table.append(list(xyz)+[rbData['rbTypes'][ivec],]+list(XYZ[ivec]))
1470                rowLabels.append(str(ivec))
1471            vecTable = G2gd.Table(table,rowLabels=rowLabels,colLabels=colLabels,types=Types)
1472            vecGrid = G2gd.GSGrid(VectorRBDisplay)
1473            vecGrid.SetTable(vecTable, True)
1474            vecGrid.Bind(wg.EVT_GRID_CELL_CHANGE, ChangeCell)
1475            if not imag:
1476                vecGrid.Bind(wg.EVT_GRID_CELL_LEFT_DCLICK, TypeSelect)
1477            attr = wx.grid.GridCellAttr()
1478            attr.SetEditor(G2gd.GridFractionEditor(vecGrid))
1479            for c in range(3):
1480                vecGrid.SetColAttr(c, attr)
1481            for row in range(vecTable.GetNumberRows()):
1482                if imag:
1483                    vecGrid.SetCellStyle(row,3,VERY_LIGHT_GREY,True)                   
1484                for col in [4,5,6]:
1485                    vecGrid.SetCellStyle(row,col,VERY_LIGHT_GREY,True)
1486            vecGrid.SetMargins(0,0)
1487            vecGrid.AutoSizeColumns(False)
1488            vecSizer.Add(vecGrid)
1489            return vecSizer
1490       
1491        def FillRefChoice(rbId,rbData):
1492            choiceIds = [i for i in range(len(rbData['rbTypes']))]
1493           
1494            rbRef = rbData.get('rbRef',[-1,-1,-1,False])
1495            for i in range(3):
1496                choiceIds.remove(rbRef[i])
1497            refChoice[rbId] = [choiceIds[:],choiceIds[:],choiceIds[:]]
1498            for i in range(3):
1499                refChoice[rbId][i].append(rbRef[i])
1500                refChoice[rbId][i].sort()     
1501           
1502        VectorRB.DestroyChildren()
1503        VectorRBDisplay = wx.Panel(VectorRB)
1504        VectorRBSizer = wx.BoxSizer(wx.VERTICAL)
1505        for rbId in data['RBIds']['Vector']:
1506            if rbId != 'AtInfo':
1507                rbData = data['Vector'][rbId]
1508                FillRefChoice(rbId,rbData)
1509                VectorRBSizer.Add(rbNameSizer(rbId,rbData),0)
1510                VectorRBSizer.Add(rbRefAtmSizer(rbId,rbData),0)
1511                XYZ = np.array([[0.,0.,0.] for Ty in rbData['rbTypes']])
1512                for imag,mag in enumerate(rbData['VectMag']):
1513                    XYZ += mag*rbData['rbVect'][imag]
1514                    VectorRBSizer.Add(rbVectMag(rbId,imag,rbData),0)
1515                    VectorRBSizer.Add(rbVectors(rbId,imag,mag,XYZ,rbData),0)
1516                VectorRBSizer.Add((5,5),0)
1517                data['Vector'][rbId]['rbXYZ'] = XYZ       
1518        VectorRBSizer.Layout()   
1519        VectorRBDisplay.SetSizer(VectorRBSizer,True)
1520        Size = VectorRBSizer.GetMinSize()
1521        Size[0] += 40
1522        Size[1] = max(Size[1],450) + 20
1523        VectorRBDisplay.SetSize(Size)
1524        VectorRB.SetScrollbars(10,10,Size[0]/10-4,Size[1]/10-1)
1525        VectorRB.Scroll(0,Scroll)
1526        Size[1] = min(Size[1],450)
1527        G2frame.dataFrame.setSizePosLeft(Size)
1528       
1529    def UpdateResidueRB():
1530        AtInfo = data['Residue']['AtInfo']
1531        refChoice = {}
1532        RefObjs = []
1533
1534        def rbNameSizer(rbId,rbData):
1535
1536            def OnRBName(event):
1537                Obj = event.GetEventObject()
1538                rbId = Indx[Obj.GetId()]
1539                rbData['RBname'] = Obj.GetValue()
1540               
1541            def OnDelRB(event):
1542                Obj = event.GetEventObject()
1543                rbId = Indx[Obj.GetId()]
1544                del data['Residue'][rbId]
1545                data['RBIds']['Residue'].remove(rbId)
1546                wx.CallAfter(UpdateResidueRB)
1547               
1548            def OnPlotRB(event):
1549                Obj = event.GetEventObject()
1550                rbId = Indx[Obj.GetId()]
1551                Obj.SetValue(False)
1552                G2plt.PlotRigidBody(G2frame,'Residue',AtInfo,rbData,plotDefaults)
1553           
1554            nameSizer = wx.BoxSizer(wx.HORIZONTAL)
1555            nameSizer.Add(wx.StaticText(ResidueRBDisplay,-1,'Residue name: '),
1556                0,wx.ALIGN_CENTER_VERTICAL)
1557            RBname = wx.TextCtrl(ResidueRBDisplay,-1,rbData['RBname'])
1558            Indx[RBname.GetId()] = rbId
1559            RBname.Bind(wx.EVT_TEXT_ENTER,OnRBName)
1560            RBname.Bind(wx.EVT_KILL_FOCUS,OnRBName)
1561            nameSizer.Add(RBname,0,wx.ALIGN_CENTER_VERTICAL)
1562            nameSizer.Add((5,0),)
1563            plotRB = wx.CheckBox(ResidueRBDisplay,-1,'Plot?')
1564            Indx[plotRB.GetId()] = rbId
1565            plotRB.Bind(wx.EVT_CHECKBOX,OnPlotRB)
1566            nameSizer.Add(plotRB,0,wx.ALIGN_CENTER_VERTICAL)
1567            nameSizer.Add((5,0),)
1568            if not rbData['useCount']:
1569                delRB = wx.CheckBox(ResidueRBDisplay,-1,'Delete?')
1570                Indx[delRB.GetId()] = rbId
1571                delRB.Bind(wx.EVT_CHECKBOX,OnDelRB)
1572                nameSizer.Add(delRB,0,wx.ALIGN_CENTER_VERTICAL)
1573            return nameSizer
1574           
1575        def rbResidues(rbId,rbData):
1576           
1577            def TypeSelect(event):
1578                AtInfo = data['Residue']['AtInfo']
1579                r,c = event.GetRow(),event.GetCol()
1580                if vecGrid.GetColLabelValue(c) == 'Type':
1581                    PE = G2elemGUI.PickElement(G2frame,oneOnly=True)
1582                    if PE.ShowModal() == wx.ID_OK:
1583                        if PE.Elem != 'None':
1584                            El = PE.Elem.strip().lower().capitalize()
1585                            if El not in AtInfo:
1586                                Info = G2elem.GetAtomInfo(El)
1587                                AtInfo[El] = [Info['Drad']['Color']]
1588                            rbData['rbTypes'][r] = El
1589                            vecGrid.SetCellValue(r,c,El)
1590                    PE.Destroy()
1591
1592            def ChangeCell(event):
1593                r,c =  event.GetRow(),event.GetCol()
1594                if r >= 0 and (0 <= c < 3):
1595                    try:
1596                        val = float(vecGrid.GetCellValue(r,c))
1597                        rbData['rbVect'][imag][r][c] = val
1598                    except ValueError:
1599                        pass
1600                       
1601            def RowSelect(event):
1602                r,c =  event.GetRow(),event.GetCol()
1603                if c < 0:                   #only row clicks
1604                    for vecgrid in resList:
1605                        vecgrid.ClearSelection()
1606                    vecGrid.SelectRow(r,True)
1607
1608            def OnRefSel(event):
1609                Obj = event.GetEventObject()
1610                iref,res,jref = Indx[Obj.GetId()]
1611                sel = Obj.GetValue()
1612                ind = atNames.index(sel)
1613                rbData['rbRef'][iref] = ind
1614                FillRefChoice(rbId,rbData)
1615                for i,ref in enumerate(RefObjs[jref]):
1616                    ref.SetItems([atNames[j] for j in refChoice[rbId][i]])
1617                    ref.SetValue(atNames[rbData['rbRef'][i]])
1618                if not iref:     #origin change
1619                    rbXYZ = rbData['rbXYZ']
1620                    rbXYZ -= rbXYZ[ind]
1621                    res.ClearSelection()
1622                    resTable = res.GetTable()
1623                    for r in range(res.GetNumberRows()):
1624                        row = resTable.GetRowValues(r)
1625                        row[2:4] = rbXYZ[r]
1626                        resTable.SetRowValues(r,row)
1627                    res.ForceRefresh()
1628                    G2plt.PlotRigidBody(G2frame,'Residue',AtInfo,rbData,plotDefaults)
1629               
1630            Types = 2*[wg.GRID_VALUE_STRING,]+3*[wg.GRID_VALUE_FLOAT+':10,5',]
1631            colLabels = ['Name','Type','Cart x','Cart y','Cart z']
1632            table = []
1633            rowLabels = []
1634            for ivec,xyz in enumerate(rbData['rbXYZ']):
1635                table.append([rbData['atNames'][ivec],]+[rbData['rbTypes'][ivec],]+list(xyz))
1636                rowLabels.append(str(ivec))
1637            vecTable = G2gd.Table(table,rowLabels=rowLabels,colLabels=colLabels,types=Types)
1638            vecGrid = G2gd.GSGrid(ResidueRBDisplay)
1639            Indx[vecGrid.GetId()] = rbId
1640            resList.append(vecGrid)
1641            vecGrid.SetTable(vecTable, True)
1642            vecGrid.Bind(wg.EVT_GRID_CELL_CHANGE, ChangeCell)
1643            vecGrid.Bind(wg.EVT_GRID_CELL_LEFT_DCLICK, TypeSelect)
1644            vecGrid.Bind(wg.EVT_GRID_LABEL_LEFT_CLICK, RowSelect)
1645            attr = wx.grid.GridCellAttr()
1646            attr.SetEditor(G2gd.GridFractionEditor(vecGrid))
1647            for c in range(3):
1648                vecGrid.SetColAttr(c, attr)
1649            for row in range(vecTable.GetNumberRows()):
1650                for col in range(5):
1651                    vecGrid.SetCellStyle(row,col,VERY_LIGHT_GREY,True)
1652            vecGrid.SetMargins(0,0)
1653            vecGrid.AutoSizeColumns(False)
1654            vecSizer = wx.BoxSizer()
1655            vecSizer.Add(vecGrid)
1656           
1657            refAtmSizer = wx.BoxSizer(wx.HORIZONTAL)
1658            atNames = rbData['atNames']
1659            rbRef = rbData['rbRef']
1660            if rbData['rbRef'][3] or rbData['useCount']:
1661                refAtmSizer.Add(wx.StaticText(ResidueRBDisplay,-1,
1662                    'Orientation reference atoms A-B-C: %s, %s, %s'%(atNames[rbRef[0]], \
1663                     atNames[rbRef[1]],atNames[rbRef[2]])),0)
1664            else:
1665                refAtmSizer.Add(wx.StaticText(ResidueRBDisplay,-1,
1666                    'Orientation reference atoms A-B-C: '),0,wx.ALIGN_CENTER_VERTICAL)
1667                refObj = [0,0,0]
1668                for i in range(3):
1669                    choices = [atNames[j] for j in refChoice[rbId][i]]
1670                    refSel = wx.ComboBox(ResidueRBDisplay,-1,value='',
1671                        choices=choices,style=wx.CB_READONLY|wx.CB_DROPDOWN)
1672                    refSel.SetValue(atNames[rbRef[i]])
1673                    refSel.Bind(wx.EVT_COMBOBOX, OnRefSel)
1674                    Indx[refSel.GetId()] = [i,vecGrid,len(RefObjs)]
1675                    refObj[i] = refSel
1676                    refAtmSizer.Add(refSel,0,wx.ALIGN_CENTER_VERTICAL)
1677                RefObjs.append(refObj)
1678           
1679            mainSizer = wx.BoxSizer(wx.VERTICAL)
1680            mainSizer.Add(refAtmSizer)
1681            mainSizer.Add(vecSizer)
1682            return mainSizer
1683           
1684        def SeqSizer(angSlide,rbId,iSeq,Seq,atNames):
1685           
1686            def ChangeAngle(event):
1687                Obj = event.GetEventObject()
1688                rbId,Seq = Indx[Obj.GetId()][:2]
1689                val = Seq[2]
1690                try:
1691                    val = float(Obj.GetValue())
1692                    Seq[2] = val
1693                except ValueError:
1694                    pass
1695                Obj.SetValue('%8.2f'%(val))
1696                G2plt.PlotRigidBody(G2frame,'Residue',AtInfo,data['Residue'][rbId],plotDefaults)
1697               
1698            def OnRadBtn(event):
1699                Obj = event.GetEventObject()
1700                Seq,iSeq,angId = Indx[Obj.GetId()]
1701                data['Residue'][rbId]['SelSeq'] = [iSeq,angId]
1702                angSlide.SetValue(int(100*Seq[2]))
1703               
1704            def OnDelBtn(event):
1705                Obj = event.GetEventObject()
1706                rbId,Seq = Indx[Obj.GetId()]
1707                data['Residue'][rbId]['rbSeq'].remove(Seq)       
1708                wx.CallAfter(UpdateResidueRB)
1709           
1710            seqSizer = wx.FlexGridSizer(0,5,2,2)
1711            seqSizer.AddGrowableCol(3,0)
1712            iBeg,iFin,angle,iMove = Seq
1713            ang = wx.TextCtrl(ResidueRBDisplay,-1,'%8.2f'%(angle),size=(50,20))
1714            if not iSeq:
1715                radBt = wx.RadioButton(ResidueRBDisplay,-1,'',style=wx.RB_GROUP)
1716                data['Residue'][rbId]['SelSeq'] = [iSeq,ang.GetId()]
1717            else:
1718                radBt = wx.RadioButton(ResidueRBDisplay,-1,'')
1719            radBt.Bind(wx.EVT_RADIOBUTTON,OnRadBtn)                   
1720            seqSizer.Add(radBt)
1721            delBt = wx.RadioButton(ResidueRBDisplay,-1,'')
1722            delBt.Bind(wx.EVT_RADIOBUTTON,OnDelBtn)
1723            seqSizer.Add(delBt)
1724            bond = wx.TextCtrl(ResidueRBDisplay,-1,'%s %s'%(atNames[iBeg],atNames[iFin]),size=(50,20))
1725            seqSizer.Add(bond,0,wx.ALIGN_CENTER_VERTICAL)
1726            Indx[radBt.GetId()] = [Seq,iSeq,ang.GetId()]
1727            Indx[delBt.GetId()] = [rbId,Seq]
1728            Indx[ang.GetId()] = [rbId,Seq,ang]
1729            ang.Bind(wx.EVT_TEXT_ENTER,ChangeAngle)
1730            ang.Bind(wx.EVT_KILL_FOCUS,ChangeAngle)
1731            seqSizer.Add(ang,0,wx.ALIGN_CENTER_VERTICAL)
1732            atms = ''
1733            for i in iMove:   
1734                atms += ' %s,'%(atNames[i])
1735            moves = wx.TextCtrl(ResidueRBDisplay,-1,atms[:-1],size=(200,20))
1736            seqSizer.Add(moves,1,wx.ALIGN_CENTER_VERTICAL|wx.EXPAND|wx.RIGHT)
1737            return seqSizer
1738           
1739        def SlideSizer():
1740           
1741            def OnSlider(event):
1742                Obj = event.GetEventObject()
1743                rbData = Indx[Obj.GetId()]
1744                iSeq,angId = rbData['SelSeq']
1745                val = float(Obj.GetValue())/100.
1746                rbData['rbSeq'][iSeq][2] = val
1747                Indx[angId][2].SetValue('%8.2f'%(val))
1748                G2plt.PlotRigidBody(G2frame,'Residue',AtInfo,rbData,plotDefaults)
1749           
1750            slideSizer = wx.BoxSizer(wx.HORIZONTAL)
1751            slideSizer.Add(wx.StaticText(ResidueRBDisplay,-1,'Selected torsion angle:'),0)
1752            iSeq,angId = rbData['SelSeq']
1753            angSlide = wx.Slider(ResidueRBDisplay,-1,
1754                int(100*rbData['rbSeq'][iSeq][2]),0,36000,size=(200,20),
1755                style=wx.SL_HORIZONTAL)
1756            angSlide.Bind(wx.EVT_SLIDER, OnSlider)
1757            Indx[angSlide.GetId()] = rbData
1758            slideSizer.Add(angSlide,0)           
1759            return slideSizer,angSlide
1760           
1761        def FillRefChoice(rbId,rbData):
1762            choiceIds = [i for i in range(len(rbData['atNames']))]
1763            for seq in rbData['rbSeq']:
1764                for i in seq[3]:
1765                    try:
1766                        choiceIds.remove(i)
1767                    except ValueError:
1768                        pass
1769            rbRef = rbData['rbRef']
1770            for i in range(3):
1771                try:
1772                    choiceIds.remove(rbRef[i])
1773                except ValueError:
1774                    pass
1775            refChoice[rbId] = [choiceIds[:],choiceIds[:],choiceIds[:]]
1776            for i in range(3):
1777                refChoice[rbId][i].append(rbRef[i])
1778                refChoice[rbId][i].sort()     
1779           
1780        ResidueRB.DestroyChildren()
1781        ResidueRBDisplay = wx.Panel(ResidueRB)
1782        ResidueRBSizer = wx.BoxSizer(wx.VERTICAL)
1783        for rbId in data['RBIds']['Residue']:
1784            rbData = data['Residue'][rbId]
1785            FillRefChoice(rbId,rbData)
1786            ResidueRBSizer.Add(rbNameSizer(rbId,rbData),0)
1787            ResidueRBSizer.Add(rbResidues(rbId,rbData),0)
1788            ResidueRBSizer.Add((5,5),0)
1789            if rbData['rbSeq']:
1790                slideSizer,angSlide = SlideSizer()
1791            if len(rbData['rbSeq']):
1792                ResidueRBSizer.Add(wx.StaticText(ResidueRBDisplay,-1,
1793                    'Sel  Del  Bond             Angle      Riding atoms'),
1794                    0,wx.ALIGN_CENTER_VERTICAL)                       
1795            for iSeq,Seq in enumerate(rbData['rbSeq']):
1796                ResidueRBSizer.Add(SeqSizer(angSlide,rbId,iSeq,Seq,rbData['atNames']))
1797            if rbData['rbSeq']:
1798                ResidueRBSizer.Add(slideSizer,)
1799            ResidueRBSizer.Add(wx.StaticText(ResidueRBDisplay,-1,100*'-'))
1800
1801        ResidueRBSizer.Add((5,25),)
1802        ResidueRBSizer.Layout()   
1803        ResidueRBDisplay.SetSizer(ResidueRBSizer,True)
1804        Size = ResidueRBSizer.GetMinSize()
1805        Size[0] += 40
1806        Size[1] = max(Size[1],450) + 20
1807        ResidueRBDisplay.SetSize(Size)
1808        ResidueRB.SetScrollbars(10,10,Size[0]/10-4,Size[1]/10-1)
1809        Size[1] = min(600,Size[1])
1810        G2frame.dataFrame.setSizePosLeft(Size)
1811       
1812    def SetStatusLine(text):
1813        Status.SetStatusText(text)                                     
1814
1815    if G2frame.dataDisplay:
1816        G2frame.dataDisplay.Destroy()
1817    G2gd.SetDataMenuBar(G2frame,G2frame.dataFrame.RigidBodyMenu)
1818    G2frame.dataFrame.SetLabel('Rigid bodies')
1819    if not G2frame.dataFrame.GetStatusBar():
1820        Status = G2frame.dataFrame.CreateStatusBar()
1821    SetStatusLine('')
1822
1823    G2frame.dataDisplay = G2gd.GSNoteBook(parent=G2frame.dataFrame,size=G2frame.dataFrame.GetClientSize())
1824
1825    VectorRB = wx.ScrolledWindow(G2frame.dataDisplay)
1826    G2frame.dataDisplay.AddPage(VectorRB,'Vector rigid bodies')
1827    ResidueRB = wx.ScrolledWindow(G2frame.dataDisplay)
1828    G2frame.dataDisplay.AddPage(ResidueRB,'Residue rigid bodies')
1829    UpdateVectorRB()
1830    G2frame.dataDisplay.Bind(wx.aui.EVT_AUINOTEBOOK_PAGE_CHANGED, OnPageChanged)
1831    OnPageChanged(None)
Note: See TracBrowser for help on using the repository browser.