source: trunk/GSASIIconstrGUI.py @ 1077

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

cleanup plot & svn bugs; set missing keywords; CIF export done; update docs

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Author Revision URL Id
File size: 63.7 KB
Line 
1# -*- coding: utf-8 -*-
2#GSASIIconstrGUI - constraint GUI routines
3########### SVN repository information ###################
4# $Date: 2013-10-03 18:11:47 +0000 (Thu, 03 Oct 2013) $
5# $Author: toby $
6# $Revision: 1077 $
7# $URL: trunk/GSASIIconstrGUI.py $
8# $Id: GSASIIconstrGUI.py 1077 2013-10-03 18:11:47Z 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 time
21import random as ran
22import numpy as np
23import numpy.ma as ma
24import os.path
25import GSASIIpath
26GSASIIpath.SetVersionNumber("$Revision: 1077 $")
27import GSASIIElem as G2elem
28import GSASIIElemGUI as G2elemGUI
29import GSASIIstrIO as G2stIO
30import GSASIImapvars as G2mv
31import GSASIIgrid as G2gd
32import GSASIIplot as G2plt
33VERY_LIGHT_GREY = wx.Colour(235,235,235)
34
35class MultiIntegerDialog(wx.Dialog):
36    '''Input a series of integers based on prompts
37    '''
38    def __init__(self,parent,title,prompts,values):
39        wx.Dialog.__init__(self,parent,-1,title, 
40            pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE)
41        self.panel = wx.Panel(self)         #just a dummy - gets destroyed in Draw!
42        self.values = values
43        self.prompts = prompts
44        self.Draw()
45       
46    def Draw(self):
47       
48        def OnValItem(event):
49            Obj = event.GetEventObject()
50            ind = Indx[Obj.GetId()]
51            try:
52                val = int(Obj.GetValue())
53                if val <= 0:
54                    raise ValueError
55            except ValueError:
56                val = self.values[ind]
57            self.values[ind] = val
58            Obj.SetValue('%d'%(val))
59           
60        self.panel.Destroy()
61        self.panel = wx.Panel(self)
62        mainSizer = wx.BoxSizer(wx.VERTICAL)
63        Indx = {}
64        for ival,[prompt,value] in enumerate(zip(self.prompts,self.values)):
65            mainSizer.Add(wx.StaticText(self.panel,-1,prompt),0,wx.ALIGN_CENTER)
66            valItem = wx.TextCtrl(self.panel,-1,value='%d'%(value),style=wx.TE_PROCESS_ENTER)
67            mainSizer.Add(valItem,0,wx.ALIGN_CENTER)
68            Indx[valItem.GetId()] = ival
69            valItem.Bind(wx.EVT_TEXT_ENTER,OnValItem)
70            valItem.Bind(wx.EVT_KILL_FOCUS,OnValItem)
71        OkBtn = wx.Button(self.panel,-1,"Ok")
72        OkBtn.Bind(wx.EVT_BUTTON, self.OnOk)
73        CancelBtn = wx.Button(self.panel,-1,'Cancel')
74        CancelBtn.Bind(wx.EVT_BUTTON, self.OnCancel)
75        btnSizer = wx.BoxSizer(wx.HORIZONTAL)
76        btnSizer.Add((20,20),1)
77        btnSizer.Add(OkBtn)
78        btnSizer.Add(CancelBtn)
79        btnSizer.Add((20,20),1)
80        mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP, 10)
81        self.panel.SetSizer(mainSizer)
82        self.panel.Fit()
83        self.Fit()
84
85    def GetValues(self):
86        return self.values
87       
88    def OnOk(self,event):
89        parent = self.GetParent()
90        parent.Raise()
91        self.EndModal(wx.ID_OK)             
92       
93    def OnCancel(self,event):
94        parent = self.GetParent()
95        parent.Raise()
96        self.EndModal(wx.ID_CANCEL)
97       
98################################################################################
99#####  Constraints
100################################################################################           
101       
102def UpdateConstraints(G2frame,data):
103    '''Called when Constraints tree item is selected.
104    Displays the constraints in the data window
105    '''
106    if not data:
107        data.update({'Hist':[],'HAP':[],'Phase':[],'Global':[]})       #empty dict - fill it
108    if 'Global' not in data:                                            #patch
109        data['Global'] = []
110    Histograms,Phases = G2frame.GetUsedHistogramsAndPhasesfromTree()
111    rigidbodyDict = G2frame.PatternTree.GetItemPyData(   
112        G2gd.GetPatternTreeItemId(G2frame,G2frame.root,'Rigid bodies'))
113    rbIds = rigidbodyDict.get('RBIds',{'Vector':[],'Residue':[]})
114    rbVary,rbDict = G2stIO.GetRigidBodyModels(rigidbodyDict,Print=False)
115    globalList = rbDict.keys()
116    globalList.sort()
117    try:
118        AtomDict = dict([Phases[phase]['pId'],Phases[phase]['Atoms']] for phase in Phases)
119    except KeyError:
120        G2frame.ErrorDialog('Constraint Error','You must run least squares at least once before setting constraints\n'+ \
121            'We suggest you refine scale factor first')
122        return
123    Natoms,atomIndx,phaseVary,phaseDict,pawleyLookup,FFtable,BLtable = G2stIO.GetPhaseData(Phases,rbIds=rbIds,Print=False)
124    phaseList = []
125    for item in phaseDict:
126        if item.split(':')[2] not in ['Ax','Ay','Az','Amul','AI/A','Atype','SHorder']:
127            phaseList.append(item)
128    phaseList.sort()
129    phaseAtNames = {}
130    phaseAtTypes = {}
131    TypeList = []
132    for item in phaseList:
133        Split = item.split(':')
134        if Split[2][:2] in ['AU','Af','dA']:
135            Id = int(Split[0])
136            phaseAtNames[item] = AtomDict[Id][int(Split[3])][0]
137            phaseAtTypes[item] = AtomDict[Id][int(Split[3])][1]
138            if phaseAtTypes[item] not in TypeList:
139                TypeList.append(phaseAtTypes[item])
140        else:
141            phaseAtNames[item] = ''
142            phaseAtTypes[item] = ''
143           
144    hapVary,hapDict,controlDict = G2stIO.GetHistogramPhaseData(Phases,Histograms,Print=False)
145    hapList = hapDict.keys()
146    hapList.sort()
147    histVary,histDict,controlDict = G2stIO.GetHistogramData(Histograms,Print=False)
148    histList = []
149    for item in histDict:
150        if item.split(':')[2] not in ['Omega','Type','Chi','Phi','Azimuth','Gonio. radius','Lam1','Lam2','Back']:
151            histList.append(item)
152    histList.sort()
153    Indx = {}
154    scope = {}                          #filled out later
155    G2frame.Page = [0,'phs']
156   
157    def GetPHlegends(Phases,Histograms):
158        plegend = '\n In p::name'
159        hlegend = '\n In :h:name'
160        phlegend = '\n In p:h:name'
161        glegend = '\n In ::name'
162        for phase in Phases:
163            plegend += '\n p:: = '+str(Phases[phase]['pId'])+':: for '+phase
164            count = 0
165            for histogram in Phases[phase]['Histograms']:
166                if count < 3:
167                    phlegend += '\n p:h: = '+str(Phases[phase]['pId'])+':'+str(Histograms[histogram]['hId'])+': for '+phase+' in '+histogram
168                else:
169                    phlegend += '\n ... etc.'
170                    break
171                count += 1
172        count = 0
173        for histogram in Histograms:
174            if count < 3:
175                hlegend += '\n :h: = :'+str(Histograms[histogram]['hId'])+': for '+histogram
176            else:
177                hlegend += '\n ... etc.'
178                break
179            count += 1
180        return plegend,hlegend,phlegend,glegend
181       
182    def FindEquivVarb(name,nameList):
183        outList = []
184        phlist = []
185        items = name.split(':')
186        namelist = [items[2],]
187        if 'dA' in name:
188            namelist = ['dAx','dAy','dAz']
189        elif 'AU' in name:
190            namelist = ['AUiso','AU11','AU22','AU33','AU12','AU13','AU23']
191        elif 'RB' in name:
192            rbfx = 'RB'+items[2][2]
193            if 'T' in name and 'Tr' not in name:
194                namelist = [rbfx+'T11',rbfx+'T22',rbfx+'T33',rbfx+'T12',rbfx+'T13',rbfx+'T23']
195            if 'L' in name:
196                namelist = [rbfx+'L11',rbfx+'L22',rbfx+'L33',rbfx+'L12',rbfx+'L13',rbfx+'L23']
197            if 'S' in name:
198                namelist = [rbfx+'S12',rbfx+'S13',rbfx+'S21',rbfx+'S23',rbfx+'S31',rbfx+'S32',rbfx+'SAA',rbfx+'SBB']
199            if 'U' in name:
200                namelist = [rbfx+'U',]
201        for item in nameList:
202            keys = item.split(':')
203            if keys[0] not in phlist:
204                phlist.append(keys[0])
205            if keys[2] in namelist and item != name:
206                outList.append(item)
207        if items[1]:
208            for key in phlist:
209                outList.append(key+':all:'+items[2])
210        return outList
211       
212    def SelectVarbs(page,FrstVarb,varList,legend,constType):
213        '''Select variables used in Constraints after one variable has
214        been selected which determines the appropriate variables to be
215        used here. Then creates the constraint and adds it to the
216        constraints list.
217        Called from OnAddEquivalence, OnAddFunction & OnAddConstraint
218        '''
219        #future -  add 'all:all:name', '0:all:name', etc. to the varList
220        if page[1] == 'phs':
221            atchoice = [item+' for '+phaseAtNames[item] for item in varList]
222            if 'RB' not in FrstVarb:
223                atchoice += [FrstVarb+' for all']
224                atchoice += [FrstVarb+' for all '+atype for atype in TypeList]
225            dlg = wx.MultiChoiceDialog(G2frame,'Select more variables:'+legend,
226                'Constrain '+FrstVarb+' and...',atchoice)
227        else:
228            dlg = wx.MultiChoiceDialog(G2frame,'Select more variables:'+legend,
229                'Constrain '+FrstVarb+' and...',varList)
230        varbs = [FrstVarb,]
231        if dlg.ShowModal() == wx.ID_OK:
232            sel = dlg.GetSelections()
233            try:
234                for x in sel:
235                    if ':all:' in varList[x]:       #a histogram 'all' - supercedes any individual selection
236                        varbs = [FrstVarb,]
237                        items = varList[x].split(':')
238                        for item in varList:
239                            if items[0] == item.split(':')[0] and ':all:' not in item:
240                                varbs.append(item)
241                        break
242                    else:
243                        varbs.append(varList[x])
244            except IndexError:      # one of the 'all' chosen - supercedes any individual selection
245                varbs = [FrstVarb,]
246                Atypes = []
247                for x in sel:
248                    item = atchoice[x]
249                    if 'all' in item:
250                        Atypes.append(item.split('all')[1].strip())
251                if '' in Atypes:
252                    varbs += varList
253                else:
254                    for item in varList:
255                        if phaseAtTypes[item] in Atypes:
256                            varbs.append(item) 
257        dlg.Destroy()
258        if len(varbs) > 1:
259            if 'equivalence' in constType:
260                constr = [[1.0,FrstVarb]]
261                for item in varbs[1:]:
262                    constr += [[1.0,item]]
263                return [constr+[None,None,'e']]      # list of equivalent variables & mults
264            elif 'function' in constType:
265                constr = map(list,zip([1.0 for i in range(len(varbs))],varbs))
266                return [constr+[None,False,'f']]         #just one constraint
267            else:       #'constraint'
268                constr = map(list,zip([1.0 for i in range(len(varbs))],varbs))
269                return [constr+[1.0,None,'c']]          #just one constraint - default sum to one
270        return []
271
272    def CheckAddedConstraint(newcons):
273        '''Check a new constraint that has just been input.
274        If there is an error display a message and give the user a
275        choice to keep or discard the last entry (why keep? -- they
276        may want to delete something else or edit multipliers).
277        Since the varylist is not available, no warning messages
278        should be generated.
279        Returns True if constraint should be added
280        '''
281        allcons = []
282        for key in ['Hist','HAP','Phase','Global']:
283            allcons += data[key]
284        allcons += newcons
285        if not len(allcons): return True
286        G2mv.InitVars()   
287        constDictList,fixedList,ignored = G2stIO.ProcessConstraints(allcons)
288        errmsg, warnmsg = G2mv.CheckConstraints('',constDictList,fixedList)
289        if errmsg:
290            res = G2frame.ErrorDialog('Constraint Error',
291                'Error with newly added constraint:\n'+errmsg+
292                '\n\nDiscard newly added constraint?',parent=G2frame.dataFrame,
293                wtype=wx.YES_NO)
294            return res != wx.ID_YES
295        elif warnmsg:
296            print 'Unexpected contraint warning:\n',warnmsg
297        return True
298
299    def CheckChangedConstraint():
300        '''Check all constraints after an edit has been made.
301        If there is an error display a message and give the user a
302        choice to keep or discard the last edit.
303        Since the varylist is not available, no warning messages
304        should be generated.
305        Returns True if the edit should be retained
306        '''
307        allcons = []
308        for key in 'Hist','HAP','Phase':
309            allcons += data[key]
310        if not len(allcons): return True
311        G2mv.InitVars()   
312        constDictList,fixedList,ignored = G2stIO.ProcessConstraints(allcons)
313        errmsg, warnmsg = G2mv.CheckConstraints('',constDictList,fixedList)
314        if errmsg:
315            res = G2frame.ErrorDialog('Constraint Error',
316                'Error after editing constraint:\n'+errmsg+
317                '\n\nDiscard last constraint edit?',parent=G2frame.dataFrame,
318                wtype=wx.YES_NO)
319            return res != wx.ID_YES
320        elif warnmsg:
321            print 'Unexpected contraint warning:\n',warnmsg
322        return True
323             
324    def OnAddHold(event):
325        '''add a Hold constraint'''
326        for phase in Phases:
327            Phase = Phases[phase]
328            Atoms = Phase['Atoms']
329        constr = []
330        page = G2frame.Page
331        choice = scope[page[1]]
332        if page[1] == 'phs':
333            atchoice = [item+' for '+phaseAtNames[item] for item in choice[2]]
334            dlg = wx.SingleChoiceDialog(G2frame,'Select 1st variable:'+choice[1],choice[0],atchoice)
335        else:   
336            dlg = wx.SingleChoiceDialog(G2frame,'Select 1st variable:'+choice[1],choice[0],choice[2])
337        if dlg.ShowModal() == wx.ID_OK:
338            sel = dlg.GetSelection()
339            FrstVarb = choice[2][sel]
340            newcons = [[[0.0,FrstVarb],None,None,'h']]
341            if CheckAddedConstraint(newcons):
342                data[choice[3]] += newcons
343        dlg.Destroy()
344        choice[4]()
345       
346    def OnAddEquivalence(event):
347        '''add an Equivalence constraint'''
348        constr = []
349        page = G2frame.Page
350        choice = scope[page[1]]
351        if page[1] == 'phs':
352            atchoice = [item+' for '+phaseAtNames[item] for item in choice[2]]
353            dlg = wx.SingleChoiceDialog(G2frame,'Select 1st variable:'+choice[1],choice[0],atchoice)
354        else:   
355            dlg = wx.SingleChoiceDialog(G2frame,'Select 1st variable:'+choice[1],choice[0],choice[2])
356        if dlg.ShowModal() == wx.ID_OK:
357            sel = dlg.GetSelection()
358            FrstVarb = choice[2][sel]
359            moreVarb = FindEquivVarb(FrstVarb,choice[2])
360            newcons = SelectVarbs(page,FrstVarb,moreVarb,choice[1],'equivalence')
361            if len(newcons) > 0:
362                if CheckAddedConstraint(newcons):
363                    data[choice[3]] += newcons
364        dlg.Destroy()
365        choice[4]()
366   
367    def OnAddFunction(event):
368        '''add a Function (new variable) constraint'''
369        constr = []
370        page = G2frame.Page
371        choice = scope[page[1]]
372        if page[1] == 'phs':
373            atchoice = [item+' for '+phaseAtNames[item] for item in choice[2]]
374            dlg = wx.SingleChoiceDialog(G2frame,'Select 1st variable:'+choice[1],choice[0],atchoice)
375        else:   
376            dlg = wx.SingleChoiceDialog(G2frame,'Select 1st variable:'+choice[1],choice[0],choice[2])
377        if dlg.ShowModal() == wx.ID_OK:
378            sel = dlg.GetSelection()
379            FrstVarb = choice[2][sel]
380            moreVarb = FindEquivVarb(FrstVarb,choice[2])
381            newcons = SelectVarbs(page,FrstVarb,moreVarb,choice[1],'function')
382            if len(newcons) > 0:
383                if CheckAddedConstraint(newcons):
384                    data[choice[3]] += newcons
385        dlg.Destroy()
386        choice[4]()
387                       
388    def OnAddConstraint(event):
389        '''add a constraint equation to the constraints list'''
390        constr = []
391        page = G2frame.Page
392        choice = scope[page[1]]
393        if page[1] == 'phs':
394            atchoice = [item+' for '+phaseAtNames[item] for item in choice[2]]
395            dlg = wx.SingleChoiceDialog(G2frame,'Select 1st variable:'+choice[1],choice[0],atchoice)
396        else:   
397            dlg = wx.SingleChoiceDialog(G2frame,'Select 1st variable:'+choice[1],choice[0],choice[2])
398        if dlg.ShowModal() == wx.ID_OK:
399            sel = dlg.GetSelection()
400            FrstVarb = choice[2][sel]
401            moreVarb = FindEquivVarb(FrstVarb,choice[2])
402            newcons = SelectVarbs(page,FrstVarb,moreVarb,choice[1],'constraint')
403            if len(newcons) > 0:
404                if CheckAddedConstraint(newcons):
405                    data[choice[3]] += newcons
406        dlg.Destroy()
407        choice[4]()
408                       
409    def ConstSizer(name,pageDisplay):
410        '''This creates a sizer displaying all of the constraints entered
411        '''
412        constSizer = wx.FlexGridSizer(1,4,0,0)
413        maxlen = 70 # characters before wrapping a constraint
414        for Id,item in enumerate(data[name]):
415            eqString = ['',]
416            if item[-1] == 'h':
417                constSizer.Add((5,5),0)              # blank space for edit button
418                typeString = ' FIXED   '
419                eqString[-1] = item[0][1]+'   '
420            elif isinstance(item[-1],str):
421                constEdit = wx.Button(pageDisplay,-1,'Edit',style=wx.BU_EXACTFIT)
422                constEdit.Bind(wx.EVT_BUTTON,OnConstEdit)
423                constSizer.Add(constEdit)            # edit button
424                Indx[constEdit.GetId()] = [Id,name]
425                if item[-1] == 'f':
426                    for term in item[:-3]:
427                        if len(eqString[-1]) > maxlen:
428                            eqString.append(' ')
429                        m = term[0]
430                        if eqString[-1] != '':
431                            if m >= 0:
432                                eqString[-1] += ' + '
433                            else:
434                                eqString[-1] += ' - '
435                                m = abs(m)
436                        eqString[-1] += '%.3f*%s '%(m,term[1])
437                    typeString = ' NEWVAR  '
438                    eqString[-1] += ' = New Variable   '
439                elif item[-1] == 'c':
440                    for term in item[:-3]:
441                        if len(eqString[-1]) > maxlen:
442                            eqString.append(' ')
443                        if eqString[-1] != '':
444                            if term[0] > 0:
445                                eqString[-1] += ' + '
446                            else:
447                                eqString[-1] += ' - '
448                        eqString[-1] += '%.3f*%s '%(abs(term[0]),term[1])
449                    typeString = ' CONSTR  '
450                    eqString[-1] += ' = %.3f'%(item[-3])+'  '
451                elif item[-1] == 'e':
452                    for term in item[:-3]:
453                        if term[0] == 0: term[0] = 1.0
454                        if len(eqString[-1]) > maxlen:
455                            eqString.append(' ')
456                        if eqString[-1] == '':
457                            eqString[-1] += '%s '%(term[1])
458                            first = term[0]
459                        else:
460                            eqString[-1] += ' = %.3f*%s '%(first/term[0],term[1])
461                    typeString = ' EQUIV   '
462                else:
463                    print 'Unexpected constraint',item
464            else:
465                print 'Removing old-style constraints'
466                data[name] = []
467                return constSizer
468            constDel = wx.Button(pageDisplay,-1,'Delete',style=wx.BU_EXACTFIT)
469            constDel.Bind(wx.EVT_BUTTON,OnConstDel)
470            Indx[constDel.GetId()] = [Id,name]
471            constSizer.Add(constDel)             # delete button
472            constSizer.Add(wx.StaticText(pageDisplay,-1,typeString),0,wx.ALIGN_CENTER_VERTICAL)
473            EqSizer = wx.BoxSizer(wx.VERTICAL)
474            for s in eqString:
475                EqSizer.Add(wx.StaticText(pageDisplay,-1,s),0,wx.ALIGN_CENTER_VERTICAL)
476            constSizer.Add(EqSizer,0,wx.ALIGN_CENTER_VERTICAL)
477            # if item[-1] == 'f':
478            #     constRef = wx.CheckBox(pageDisplay,-1,label=' Refine?')
479            #     constRef.SetValue(item[-2])
480            #     constRef.Bind(wx.EVT_CHECKBOX,OnConstRef)
481            #     Indx[constRef.GetId()] = item
482            #     constSizer.Add(constRef)
483            # else:
484            #     constSizer.Add((5,5),0)
485        return constSizer
486               
487    # def OnConstRef(event):
488    #     Obj = event.GetEventObject()
489    #     Indx[Obj.GetId()][-2] = Obj.GetValue()
490       
491    def OnConstDel(event):
492        Obj = event.GetEventObject()
493        Id,name = Indx[Obj.GetId()]
494        del(data[name][Id])
495        OnPageChanged(None)       
496       
497    def OnConstEdit(event):
498        '''Called to edit an individual contraint by the Edit button'''
499        Obj = event.GetEventObject()
500        Id,name = Indx[Obj.GetId()]
501        sep = '*'
502        if data[name][Id][-1] == 'f':
503            items = data[name][Id][:-3]+[[],]
504            constType = 'New Variable'
505            lbl = 'Enter value for each term in constraint; sum = new variable'
506        elif data[name][Id][-1] == 'c':
507            items = data[name][Id][:-3]+[
508                [data[name][Id][-3],'fixed value ='],[]]
509            constType = 'Constraint'
510            lbl = 'Edit value for each term in constant constraint sum'
511        elif data[name][Id][-1] == 'e':
512            items = data[name][Id][:-3]+[[],]
513            constType = 'Equivalence'
514            lbl = 'The following terms are set to be equal:'
515            sep = '/'
516        else:
517            return
518        dlg = G2frame.ConstraintDialog(G2frame.dataFrame,constType,lbl,items,sep)
519        try:
520            if dlg.ShowModal() == wx.ID_OK:
521                prev = data[name][Id]
522                result = dlg.GetData()
523                if data[name][Id][-1] == 'c':
524                    data[name][Id][:-3] = result[:-2]
525                    data[name][Id][-3] = result[-2][0]
526                else:
527                    data[name][Id][:-3] = result[:-1]
528                if not CheckChangedConstraint():
529                    data[name][Id] = prev
530        except:
531            import traceback
532            print traceback.format_exc()
533        finally:
534            dlg.Destroy()           
535        OnPageChanged(None)                     
536   
537    def UpdateHAPConstr():
538        '''Responds to press on Histogram/Phase Constraints tab,
539        shows constraints in data window'''
540        HAPConstr.DestroyChildren()
541        HAPDisplay = wx.Panel(HAPConstr)
542        HAPSizer = wx.BoxSizer(wx.VERTICAL)
543        HAPSizer.Add((5,5),0)
544        HAPSizer.Add(ConstSizer('HAP',HAPDisplay))
545        HAPDisplay.SetSizer(HAPSizer,True)
546        Size = HAPSizer.GetMinSize()
547        Size[0] += 40
548        Size[1] = max(Size[1],250) + 20
549        HAPDisplay.SetSize(Size)
550        # scroll bar not working, at least not on Mac
551        HAPConstr.SetScrollbars(10,10,Size[0]/10-4,Size[1]/10-1)
552        Size[1] = min(Size[1],250)
553        G2frame.dataFrame.setSizePosLeft(Size)
554       
555    def UpdateHistConstr():
556        '''Responds to press on Histogram Constraints tab,
557        shows constraints in data window'''
558        HistConstr.DestroyChildren()
559        HistDisplay = wx.Panel(HistConstr)
560        HistSizer = wx.BoxSizer(wx.VERTICAL)
561        HistSizer.Add((5,5),0)       
562        HistSizer.Add(ConstSizer('Hist',HistDisplay))
563        HistDisplay.SetSizer(HistSizer,True)
564        Size = HistSizer.GetMinSize()
565        Size[0] += 40
566        Size[1] = max(Size[1],250) + 20
567        HistDisplay.SetSize(Size)
568        HistConstr.SetScrollbars(10,10,Size[0]/10-4,Size[1]/10-1)
569        Size[1] = min(Size[1],250)
570        G2frame.dataFrame.setSizePosLeft(Size)
571       
572    def UpdatePhaseConstr():
573        '''Responds to press on Phase Constraint tab,
574        shows constraints in data window'''
575        PhaseConstr.DestroyChildren()
576        PhaseDisplay = wx.Panel(PhaseConstr)
577        PhaseSizer = wx.BoxSizer(wx.VERTICAL)
578        PhaseSizer.Add((5,5),0)       
579        PhaseSizer.Add(ConstSizer('Phase',PhaseDisplay))
580        PhaseDisplay.SetSizer(PhaseSizer,True)
581        Size = PhaseSizer.GetMinSize()
582        Size[0] += 40
583        Size[1] = max(Size[1],250) + 20
584        PhaseDisplay.SetSize(Size)
585        PhaseConstr.SetScrollbars(10,10,Size[0]/10-4,Size[1]/10-1)
586        Size[1] = min(Size[1],250)
587        G2frame.dataFrame.setSizePosLeft(Size)
588
589    def UpdateGlobalConstr():
590        '''Responds to press on Global Constraint tab,
591        shows constraints in data window'''
592        GlobalConstr.DestroyChildren()
593        GlobalDisplay = wx.Panel(GlobalConstr)
594        GlobalSizer = wx.BoxSizer(wx.VERTICAL)
595        GlobalSizer.Add((5,5),0)       
596        GlobalSizer.Add(ConstSizer('Global',GlobalDisplay))
597        GlobalDisplay.SetSizer(GlobalSizer,True)
598        Size = GlobalSizer.GetMinSize()
599        Size[0] += 40
600        Size[1] = max(Size[1],250) + 20
601        GlobalDisplay.SetSize(Size)
602        GlobalConstr.SetScrollbars(10,10,Size[0]/10-4,Size[1]/10-1)
603        Size[1] = min(Size[1],250)
604        G2frame.dataFrame.setSizePosLeft(Size)
605   
606    def OnPageChanged(event):
607        if event:       #page change event!
608            page = event.GetSelection()
609        else:
610            page = G2frame.dataDisplay.GetSelection()
611        oldPage = G2frame.dataDisplay.ChangeSelection(page)
612        text = G2frame.dataDisplay.GetPageText(page)
613        if text == 'Histogram/Phase constraints':
614            G2frame.Page = [page,'hap']
615            UpdateHAPConstr()
616        elif text == 'Histogram constraints':
617            G2frame.Page = [page,'hst']
618            UpdateHistConstr()
619        elif text == 'Phase constraints':
620            G2frame.Page = [page,'phs']
621            UpdatePhaseConstr()
622        elif text == 'Global constraints':
623            G2frame.Page = [page,'glb']
624            UpdateGlobalConstr()
625           
626
627    def SetStatusLine(text):
628        Status.SetStatusText(text)                                     
629       
630    plegend,hlegend,phlegend,glegend = GetPHlegends(Phases,Histograms)
631    scope = {'hst':['Histogram contraints:',hlegend,histList,'Hist',UpdateHistConstr],
632        'hap':['Histogram * Phase contraints:',phlegend,hapList,'HAP',UpdateHAPConstr],
633        'phs':['Phase contraints:',plegend,phaseList,'Phase',UpdatePhaseConstr],
634        'glb':['Global constraints:',glegend,globalList,'Global',UpdateGlobalConstr]}
635    if G2frame.dataDisplay:
636        G2frame.dataDisplay.Destroy()
637    G2gd.SetDataMenuBar(G2frame,G2frame.dataFrame.ConstraintMenu)
638    G2frame.dataFrame.SetLabel('Constraints')
639    if not G2frame.dataFrame.GetStatusBar():
640        Status = G2frame.dataFrame.CreateStatusBar()
641    SetStatusLine('')
642   
643    G2gd.SetDataMenuBar(G2frame,G2frame.dataFrame.ConstraintMenu)
644    G2frame.dataFrame.Bind(wx.EVT_MENU, OnAddConstraint, id=G2gd.wxID_CONSTRAINTADD)
645    G2frame.dataFrame.Bind(wx.EVT_MENU, OnAddFunction, id=G2gd.wxID_FUNCTADD)
646    G2frame.dataFrame.Bind(wx.EVT_MENU, OnAddEquivalence, id=G2gd.wxID_EQUIVADD)
647    G2frame.dataFrame.Bind(wx.EVT_MENU, OnAddHold, id=G2gd.wxID_HOLDADD)
648    G2frame.dataDisplay = G2gd.GSNoteBook(parent=G2frame.dataFrame,size=G2frame.dataFrame.GetClientSize())
649   
650    PhaseConstr = wx.ScrolledWindow(G2frame.dataDisplay)
651    G2frame.dataDisplay.AddPage(PhaseConstr,'Phase constraints')
652    HAPConstr = wx.ScrolledWindow(G2frame.dataDisplay)
653    G2frame.dataDisplay.AddPage(HAPConstr,'Histogram/Phase constraints')
654    HistConstr = wx.ScrolledWindow(G2frame.dataDisplay)
655    G2frame.dataDisplay.AddPage(HistConstr,'Histogram constraints')
656    GlobalConstr = wx.ScrolledWindow(G2frame.dataDisplay)
657    G2frame.dataDisplay.AddPage(GlobalConstr,'Global constraints')   
658    UpdatePhaseConstr()
659
660    G2frame.dataDisplay.Bind(wx.aui.EVT_AUINOTEBOOK_PAGE_CHANGED, OnPageChanged)
661    # validate all the constrants -- should not see any errors here normally
662    allcons = []
663    for key in 'Hist','HAP','Phase':
664        allcons += data[key]
665    if not len(allcons): return
666    G2mv.InitVars()   
667    constDictList,fixedList,ignored = G2stIO.ProcessConstraints(allcons)
668    errmsg, warnmsg = G2mv.CheckConstraints('',constDictList,fixedList)
669    if errmsg:
670        G2frame.ErrorDialog('Constraint Error','Error in constraints:\n'+errmsg,
671            parent=G2frame.dataFrame)
672    elif warnmsg:
673        print 'Unexpected contraint warning:\n',warnmsg
674       
675################################################################################
676#### Rigid bodies
677################################################################################
678
679def UpdateRigidBodies(G2frame,data):
680    '''Called when Rigid bodies tree item is selected.
681    Displays the rigid bodies in the data window
682    '''
683    if not data.get('RBIds') or not data:
684        data.update({'Vector':{'AtInfo':{}},'Residue':{'AtInfo':{}},
685            'RBIds':{'Vector':[],'Residue':[]}})       #empty/bad dict - fill it
686           
687    global resList
688    Indx = {}
689    resList = []
690    plotDefaults = {'oldxy':[0.,0.],'Quaternion':[0.,0.,0.,1.],'cameraPos':30.,'viewDir':[0,0,1],}
691
692    def OnPageChanged(event):
693        global resList
694        resList = []
695        if event:       #page change event!
696            page = event.GetSelection()
697        else:
698            page = G2frame.dataDisplay.GetSelection()
699        oldPage = G2frame.dataDisplay.ChangeSelection(page)
700        text = G2frame.dataDisplay.GetPageText(page)
701        if text == 'Vector rigid bodies':
702            G2frame.dataFrame.RigidBodyMenu.Remove(0)   #NB: wx.MenuBar.Replace gives error
703            G2frame.dataFrame.RigidBodyMenu.Insert(0,G2frame.dataFrame.VectorRBEdit,title='Edit')
704            G2frame.Page = [page,'vrb']
705            UpdateVectorRB()
706        elif text == 'Residue rigid bodies':
707            G2frame.dataFrame.RigidBodyMenu.Remove(0)
708            G2frame.dataFrame.RigidBodyMenu.Insert(0,G2frame.dataFrame.ResidueRBMenu,title='Edit')
709            G2frame.Page = [page,'rrb']
710            UpdateResidueRB()
711        elif text == 'Z-matrix rigid bodies':
712            G2frame.dataFrame.RigidBodyMenu.Remove(0)
713            G2frame.dataFrame.RigidBodyMenu.Insert(0,G2frame.dataFrame.ZMatrixRBMenu,title='Edit')
714            G2frame.Page = [page,'zrb']
715            UpdateZMatrixRB()
716           
717    def getMacroFile(macName):
718        defDir = os.path.join(os.path.split(__file__)[0],'GSASIImacros')
719        dlg = wx.FileDialog(G2frame,message='Choose '+macName+' rigid body macro file',
720            defaultDir=defDir,defaultFile="",wildcard="GSAS-II macro file (*.mac)|*.mac",
721            style=wx.OPEN | wx.CHANGE_DIR)
722        try:
723            if dlg.ShowModal() == wx.ID_OK:
724                macfile = dlg.GetPath()
725                macro = open(macfile,'Ur')
726                head = macro.readline()
727                if macName not in head:
728                    print head
729                    print '**** ERROR - wrong restraint macro file selected, try again ****'
730                    macro = []
731            else: # cancel was pressed
732                macro = []
733        finally:
734            dlg.Destroy()
735        return macro        #advanced past 1st line
736       
737    def getTextFile():
738        defDir = os.path.join(os.path.split(__file__)[0],'GSASIImacros')
739        dlg = wx.FileDialog(G2frame,'Choose rigid body text file', '.', '',
740            "GSAS-II text file (*.txt)|*.txt|XYZ file (*.xyz)|*.xyz|"
741            "Sybyl mol2 file (*.mol2)|*.mol2|PDB file (*.pdb;*.ent)|*.pdb;*.ent",
742            wx.OPEN | wx.CHANGE_DIR)
743        try:
744            if dlg.ShowModal() == wx.ID_OK:
745                txtfile = dlg.GetPath()
746                ext = os.path.splitext(txtfile)[1]
747                text = open(txtfile,'Ur')
748            else: # cancel was pressed
749                ext = ''
750                text = []
751        finally:
752            dlg.Destroy()
753        if 'ent' in ext:
754            ext = '.pdb'
755        return text,ext.lower()
756       
757    def OnAddRigidBody(event):
758        page = G2frame.dataDisplay.GetSelection()
759        if 'Vector' in G2frame.dataDisplay.GetPageText(page):
760            AddVectorRB()
761        elif 'Residue' in G2frame.dataDisplay.GetPageText(page):
762            AddResidueRB()
763           
764    def OnImportRigidBody(event):
765        page = G2frame.dataDisplay.GetSelection()
766        if 'Vector' in G2frame.dataDisplay.GetPageText(page):
767            pass
768        elif 'Residue' in G2frame.dataDisplay.GetPageText(page):
769            ImportResidueRB()
770           
771    def AddVectorRB():
772        AtInfo = data['Vector']['AtInfo']
773        dlg = MultiIntegerDialog(G2frame.dataDisplay,'New Rigid Body',['No. atoms','No. translations'],[1,1])
774        if dlg.ShowModal() == wx.ID_OK:
775            nAtoms,nTrans = dlg.GetValues()
776            vectorRB = data['Vector']
777            rbId = ran.randint(0,sys.maxint)
778            vecMag = [1.0 for i in range(nTrans)]
779            vecRef = [False for i in range(nTrans)]
780            vecVal = [np.zeros((nAtoms,3)) for j in range(nTrans)]
781            rbTypes = ['C' for i in range(nAtoms)]
782            Info = G2elem.GetAtomInfo('C')
783            AtInfo['C'] = [Info['Drad'],Info['Color']]
784            data['Vector'][rbId] = {'RBname':'UNKRB','VectMag':vecMag,'rbXYZ':np.zeros((nAtoms,3)),
785                'rbRef':[0,1,2,False],'VectRef':vecRef,'rbTypes':rbTypes,'rbVect':vecVal,'useCount':0}
786            data['RBIds']['Vector'].append(rbId)
787        dlg.Destroy()
788        UpdateVectorRB()
789       
790    def AddResidueRB():
791        AtInfo = data['Residue']['AtInfo']
792        macro = getMacroFile('rigid body')
793        if not macro:
794            return
795        macStr = macro.readline()
796        while macStr:
797            items = macStr.split()
798            if 'I' == items[0]:
799                rbId = ran.randint(0,sys.maxint)
800                rbName = items[1]
801                rbTypes = []
802                rbXYZ = []
803                rbSeq = []
804                atNames = []
805                nAtms,nSeq,nOrig,mRef,nRef = [int(items[i]) for i in [2,3,4,5,6]]
806                for iAtm in range(nAtms):
807                    macStr = macro.readline().split()
808                    atName = macStr[0]
809                    atType = macStr[1]
810                    atNames.append(atName)
811                    rbXYZ.append([float(macStr[i]) for i in [2,3,4]])
812                    rbTypes.append(atType)
813                    if atType not in AtInfo:
814                        Info = G2elem.GetAtomInfo(atType)
815                        AtInfo[atType] = [Info['Drad'],Info['Color']]
816                rbXYZ = np.array(rbXYZ)-np.array(rbXYZ[nOrig-1])
817                for iSeq in range(nSeq):
818                    macStr = macro.readline().split()
819                    mSeq = int(macStr[0])
820                    for jSeq in range(mSeq):
821                        macStr = macro.readline().split()
822                        iBeg = int(macStr[0])-1
823                        iFin = int(macStr[1])-1
824                        angle = 0.0
825                        nMove = int(macStr[2])
826                        iMove = [int(macStr[i])-1 for i in range(3,nMove+3)]
827                        rbSeq.append([iBeg,iFin,angle,iMove])
828                data['Residue'][rbId] = {'RBname':rbName,'rbXYZ':rbXYZ,'rbTypes':rbTypes,
829                    'atNames':atNames,'rbRef':[nOrig-1,mRef-1,nRef-1,True],'rbSeq':rbSeq,
830                    'SelSeq':[0,0],'useCount':0}
831                data['RBIds']['Residue'].append(rbId)
832                print 'Rigid body '+rbName+' added'
833            macStr = macro.readline()
834        macro.close()
835        UpdateResidueRB()
836       
837    def ImportResidueRB():
838        AtInfo = data['Residue']['AtInfo']
839        text,ext = getTextFile()
840        if not text:
841            return
842        rbId = ran.randint(0,sys.maxint)
843        rbTypes = []
844        rbXYZ = []
845        rbSeq = []
846        atNames = []
847        txtStr = text.readline()
848        if 'xyz' in ext:
849            txtStr = text.readline()
850            txtStr = text.readline()
851        elif 'mol2' in ext:
852            while 'ATOM' not in txtStr:
853                txtStr = text.readline()
854            txtStr = text.readline()
855        elif 'pdb' in ext:
856            while 'ATOM' not in txtStr[:6] and 'HETATM' not in txtStr[:6]:
857                txtStr = text.readline()
858                print txtStr
859        items = txtStr.split()
860        while len(items):
861            if 'txt' in ext:
862                atName = items[0]
863                atType = items[1]
864                rbXYZ.append([float(items[i]) for i in [2,3,4]])
865            elif 'xyz' in ext:
866                atType = items[0]
867                rbXYZ.append([float(items[i]) for i in [1,2,3]])
868                atName = atType+str(len(rbXYZ))
869            elif 'mol2' in ext:
870                atType = items[1]
871                atName = items[1]+items[0]
872                rbXYZ.append([float(items[i]) for i in [2,3,4]])
873            elif 'pdb' in ext:
874                atType = items[-1]
875                atName = items[2]
876                xyz = txtStr[30:55].split()                   
877                rbXYZ.append([float(x) for x in xyz])
878            atNames.append(atName)
879            rbTypes.append(atType)
880            if atType not in AtInfo:
881                Info = G2elem.GetAtomInfo(atType)
882                AtInfo[atType] = [Info['Drad'],Info['Color']]
883            txtStr = text.readline()
884            if 'mol2' in ext and 'BOND' in txtStr:
885                break
886            if 'pdb' in ext and ('ATOM' not in txtStr[:6] and 'HETATM' not in txtStr[:6]):
887                break
888            items = txtStr.split()
889        rbXYZ = np.array(rbXYZ)-np.array(rbXYZ[0])
890        data['Residue'][rbId] = {'RBname':'UNKRB','rbXYZ':rbXYZ,'rbTypes':rbTypes,
891            'atNames':atNames,'rbRef':[0,1,2,False],'rbSeq':[],'SelSeq':[0,0],'useCount':0}
892        data['RBIds']['Residue'].append(rbId)
893        print 'Rigid body UNKRB added'
894        text.close()
895        UpdateResidueRB()
896       
897    def FindNeighbors(Orig,XYZ,atTypes,atNames,AtInfo):
898        Radii = []
899        for Atype in atTypes:
900            Radii.append(AtInfo[Atype][0])
901        Radii = np.array(Radii)
902        Neigh = []
903        Dx = XYZ-XYZ[Orig]
904        dist = np.sqrt(np.sum(Dx**2,axis=1))
905        sumR = Radii[Orig]+Radii
906        IndB = ma.nonzero(ma.masked_greater(dist-0.85*sumR,0.))
907        for j in IndB[0]:
908            if j != Orig:
909                Neigh.append(atNames[j])
910        return Neigh
911       
912    def FindAllNeighbors(XYZ,atTypes,atNames,AtInfo):
913        NeighDict = {}
914        for iat,xyz in enumerate(atNames):
915            NeighDict[atNames[iat]] = FindNeighbors(iat,XYZ,atTypes,atNames,AtInfo)
916        return NeighDict
917       
918    def FindRiding(Orig,Pivot,NeighDict):
919        riding = [Orig,Pivot]
920        iAdd = 1
921        new = True
922        while new:
923            newAtms = NeighDict[riding[iAdd]]
924            for At in newAtms:
925                new = False
926                if At not in riding:
927                    riding.append(At)
928                    new = True
929            iAdd += 1
930            if iAdd < len(riding):
931                new = True
932        return riding[2:]
933                       
934    def OnDefineTorsSeq(event):
935        rbKeys = data['Residue'].keys()
936        rbKeys.remove('AtInfo')
937        rbNames = [data['Residue'][k]['RBname'] for k in rbKeys]
938        rbIds = dict(zip(rbNames,rbKeys))
939        rbNames.sort()
940        rbId = 0
941        if len(rbNames) > 1:
942            dlg = wx.SingleChoiceDialog(G2frame,'Select rigid body for torsion sequence','Torsion sequence',rbNames)
943            if dlg.ShowModal() == wx.ID_OK:
944                sel = dlg.GetSelection()
945                rbId = rbIds[rbNames[sel]]
946                rbData = data['Residue'][rbId]
947            dlg.Destroy()
948        else:
949            rbId = rbIds[rbNames[0]]
950            rbData = data['Residue'][rbId]
951        if not len(rbData):
952            return
953        atNames = rbData['atNames']
954        AtInfo = data['Residue']['AtInfo']
955        atTypes = rbData['rbTypes']
956        XYZ = rbData['rbXYZ']
957        neighDict = FindAllNeighbors(XYZ,atTypes,atNames,AtInfo)
958        TargList = []           
959        dlg = wx.SingleChoiceDialog(G2frame,'Select origin atom for torsion sequence','Origin atom',rbData['atNames'])
960        if dlg.ShowModal() == wx.ID_OK:
961            Orig = dlg.GetSelection()
962            xyz = XYZ[Orig]
963            TargList = neighDict[atNames[Orig]]
964        dlg.Destroy()
965        if not len(TargList):
966            return
967        dlg = wx.SingleChoiceDialog(G2frame,'Select pivot atom for torsion sequence','Pivot atom',TargList)
968        if dlg.ShowModal() == wx.ID_OK:
969            Piv = atNames.index(TargList[dlg.GetSelection()])
970            riding = FindRiding(atNames[Orig],atNames[Piv],neighDict)
971            Riding = []
972            for atm in riding:
973                Riding.append(atNames.index(atm))
974            rbData['rbSeq'].append([Orig,Piv,0.0,Riding])           
975        dlg.Destroy()
976        UpdateResidueRB()
977
978    def UpdateVectorRB(Scroll=0):
979        AtInfo = data['Vector']['AtInfo']
980        refChoice = {}
981        SetStatusLine(' You may use e.g. "c60" or "s60" for a vector entry')
982        def rbNameSizer(rbId,rbData):
983
984            def OnRBName(event):
985                Obj = event.GetEventObject()
986                rbId = Indx[Obj.GetId()]
987                rbData['RBname'] = Obj.GetValue()
988               
989            def OnDelRB(event):
990                Obj = event.GetEventObject()
991                rbId = Indx[Obj.GetId()]
992                del data['Vector'][rbId]
993                data['RBIds']['Vector'].remove(rbId)
994                rbData['useCount'] -= 1
995                wx.CallAfter(UpdateVectorRB)
996               
997            def OnPlotRB(event):
998                Obj = event.GetEventObject()
999                rbId = Indx[Obj.GetId()]
1000                Obj.SetValue(False)
1001                G2plt.PlotRigidBody(G2frame,'Vector',AtInfo,rbData,plotDefaults)
1002           
1003            nameSizer = wx.BoxSizer(wx.HORIZONTAL)
1004            nameSizer.Add(wx.StaticText(VectorRBDisplay,-1,'Rigid body name: '),
1005                0,wx.ALIGN_CENTER_VERTICAL)
1006            RBname = wx.TextCtrl(VectorRBDisplay,-1,rbData['RBname'])
1007            Indx[RBname.GetId()] = rbId
1008            RBname.Bind(wx.EVT_TEXT_ENTER,OnRBName)
1009            RBname.Bind(wx.EVT_KILL_FOCUS,OnRBName)
1010            nameSizer.Add(RBname,0,wx.ALIGN_CENTER_VERTICAL)
1011            nameSizer.Add((5,0),)
1012            plotRB = wx.CheckBox(VectorRBDisplay,-1,'Plot?')
1013            Indx[plotRB.GetId()] = rbId
1014            plotRB.Bind(wx.EVT_CHECKBOX,OnPlotRB)
1015            nameSizer.Add(plotRB,0,wx.ALIGN_CENTER_VERTICAL)
1016            nameSizer.Add((5,0),)
1017            if not rbData['useCount']:
1018                delRB = wx.CheckBox(VectorRBDisplay,-1,'Delete?')
1019                Indx[delRB.GetId()] = rbId
1020                delRB.Bind(wx.EVT_CHECKBOX,OnDelRB)
1021                nameSizer.Add(delRB,0,wx.ALIGN_CENTER_VERTICAL)
1022            return nameSizer
1023           
1024        def rbRefAtmSizer(rbId,rbData):
1025           
1026            def OnRefSel(event):
1027                Obj = event.GetEventObject()
1028                iref = Indx[Obj.GetId()]
1029                sel = Obj.GetValue()
1030                rbData['rbRef'][iref] = atNames.index(sel)
1031                FillRefChoice(rbId,rbData)
1032           
1033            refAtmSizer = wx.BoxSizer(wx.HORIZONTAL)
1034            atNames = [name+str(i) for i,name in enumerate(rbData['rbTypes'])]
1035            rbRef = rbData.get('rbRef',[0,1,2,False])
1036            rbData['rbRef'] = rbRef
1037            if rbData['useCount']:
1038                refAtmSizer.Add(wx.StaticText(VectorRBDisplay,-1,
1039                    'Orientation reference atoms A-B-C: %s, %s, %s'%(atNames[rbRef[0]], \
1040                     atNames[rbRef[1]],atNames[rbRef[2]])),0)
1041            else:
1042                refAtmSizer.Add(wx.StaticText(VectorRBDisplay,-1,
1043                    'Orientation reference atoms A-B-C: '),0,wx.ALIGN_CENTER_VERTICAL)
1044                for i in range(3):
1045                    choices = [atNames[j] for j in refChoice[rbId][i]]
1046                    refSel = wx.ComboBox(VectorRBDisplay,-1,value='',
1047                        choices=choices,style=wx.CB_READONLY|wx.CB_DROPDOWN)
1048                    refSel.SetValue(atNames[rbRef[i]])
1049                    refSel.Bind(wx.EVT_COMBOBOX, OnRefSel)
1050                    Indx[refSel.GetId()] = i
1051                    refAtmSizer.Add(refSel,0,wx.ALIGN_CENTER_VERTICAL)
1052            return refAtmSizer
1053                       
1054        def rbVectMag(rbId,imag,rbData):
1055           
1056            def OnRBVectorMag(event):
1057                Obj = event.GetEventObject()
1058                rbId,imag = Indx[Obj.GetId()]
1059                try:
1060                    val = float(Obj.GetValue())
1061                    if val <= 0.:
1062                        raise ValueError
1063                    rbData['VectMag'][imag] = val
1064                except ValueError:
1065                    pass
1066                Obj.SetValue('%8.4f'%(val))
1067                wx.CallAfter(UpdateVectorRB,VectorRB.GetScrollPos(wx.VERTICAL))
1068                G2plt.PlotRigidBody(G2frame,'Vector',AtInfo,data['Vector'][rbId],plotDefaults)
1069               
1070            def OnRBVectorRef(event):
1071                Obj = event.GetEventObject()
1072                rbId,imag = Indx[Obj.GetId()]
1073                rbData['VectRef'][imag] = Obj.GetValue()
1074                       
1075            magSizer = wx.wx.BoxSizer(wx.HORIZONTAL)
1076            magSizer.Add(wx.StaticText(VectorRBDisplay,-1,'Translation magnitude: '),
1077                0,wx.ALIGN_CENTER_VERTICAL)
1078            magValue = wx.TextCtrl(VectorRBDisplay,-1,'%8.4f'%(rbData['VectMag'][imag]))
1079            Indx[magValue.GetId()] = [rbId,imag]
1080            magValue.Bind(wx.EVT_TEXT_ENTER,OnRBVectorMag)
1081            magValue.Bind(wx.EVT_KILL_FOCUS,OnRBVectorMag)
1082            magSizer.Add(magValue,0,wx.ALIGN_CENTER_VERTICAL)
1083            magSizer.Add((5,0),)
1084            magref = wx.CheckBox(VectorRBDisplay,-1,label=' Refine?') 
1085            magref.SetValue(rbData['VectRef'][imag])
1086            magref.Bind(wx.EVT_CHECKBOX,OnRBVectorRef)
1087            Indx[magref.GetId()] = [rbId,imag]
1088            magSizer.Add(magref,0,wx.ALIGN_CENTER_VERTICAL)
1089            return magSizer
1090           
1091        def rbVectors(rbId,imag,mag,XYZ,rbData):
1092
1093            def TypeSelect(event):
1094                Obj = event.GetEventObject()
1095                AtInfo = data['Vector']['AtInfo']
1096                r,c = event.GetRow(),event.GetCol()
1097                if vecGrid.GetColLabelValue(c) == 'Type':
1098                    PE = G2elemGUI.PickElement(G2frame,oneOnly=True)
1099                    if PE.ShowModal() == wx.ID_OK:
1100                        if PE.Elem != 'None':
1101                            El = PE.Elem.strip().lower().capitalize()
1102                            if El not in AtInfo:
1103                                Info = G2elem.GetAtomInfo(El)
1104                                AtInfo[El] = [Info['Drad'],Info['Color']]
1105                            rbData['rbTypes'][r] = El
1106                            vecGrid.SetCellValue(r,c,El)
1107                    PE.Destroy()
1108                wx.CallAfter(UpdateVectorRB,VectorRB.GetScrollPos(wx.VERTICAL))
1109
1110            def ChangeCell(event):
1111                Obj = event.GetEventObject()
1112                r,c =  event.GetRow(),event.GetCol()
1113                if r >= 0 and (0 <= c < 3):
1114                    try:
1115                        val = float(vecGrid.GetCellValue(r,c))
1116                        rbData['rbVect'][imag][r][c] = val
1117                    except ValueError:
1118                        pass
1119                G2plt.PlotRigidBody(G2frame,'Vector',AtInfo,data['Vector'][rbId],plotDefaults)
1120                wx.CallAfter(UpdateVectorRB,VectorRB.GetScrollPos(wx.VERTICAL))
1121
1122            vecSizer = wx.BoxSizer()
1123            Types = 3*[wg.GRID_VALUE_FLOAT+':10,5',]+[wg.GRID_VALUE_STRING,]+3*[wg.GRID_VALUE_FLOAT+':10,5',]
1124            colLabels = ['Vector x','Vector y','Vector z','Type','Cart x','Cart y','Cart z']
1125            table = []
1126            rowLabels = []
1127            for ivec,xyz in enumerate(rbData['rbVect'][imag]):
1128                table.append(list(xyz)+[rbData['rbTypes'][ivec],]+list(XYZ[ivec]))
1129                rowLabels.append(str(ivec))
1130            vecTable = G2gd.Table(table,rowLabels=rowLabels,colLabels=colLabels,types=Types)
1131            vecGrid = G2gd.GSGrid(VectorRBDisplay)
1132            vecGrid.SetTable(vecTable, True)
1133            vecGrid.Bind(wg.EVT_GRID_CELL_CHANGE, ChangeCell)
1134            if not imag:
1135                vecGrid.Bind(wg.EVT_GRID_CELL_LEFT_DCLICK, TypeSelect)
1136            attr = wx.grid.GridCellAttr()
1137            attr.SetEditor(G2gd.GridFractionEditor(vecGrid))
1138            for c in range(3):
1139                vecGrid.SetColAttr(c, attr)
1140            for row in range(vecTable.GetNumberRows()):
1141                if imag:
1142                    vecGrid.SetCellStyle(row,3,VERY_LIGHT_GREY,True)                   
1143                for col in [4,5,6]:
1144                    vecGrid.SetCellStyle(row,col,VERY_LIGHT_GREY,True)
1145            vecGrid.SetMargins(0,0)
1146            vecGrid.AutoSizeColumns(False)
1147            vecSizer.Add(vecGrid)
1148            return vecSizer
1149       
1150        def FillRefChoice(rbId,rbData):
1151            choiceIds = [i for i in range(len(rbData['rbTypes']))]
1152           
1153            rbRef = rbData.get('rbRef',[-1,-1,-1,False])
1154            for i in range(3):
1155                choiceIds.remove(rbRef[i])
1156            refChoice[rbId] = [choiceIds[:],choiceIds[:],choiceIds[:]]
1157            for i in range(3):
1158                refChoice[rbId][i].append(rbRef[i])
1159                refChoice[rbId][i].sort()     
1160           
1161        VectorRB.DestroyChildren()
1162        VectorRBDisplay = wx.Panel(VectorRB)
1163        VectorRBSizer = wx.BoxSizer(wx.VERTICAL)
1164        for rbId in data['RBIds']['Vector']:
1165            if rbId != 'AtInfo':
1166                rbData = data['Vector'][rbId]
1167                FillRefChoice(rbId,rbData)
1168                VectorRBSizer.Add(rbNameSizer(rbId,rbData),0)
1169                VectorRBSizer.Add(rbRefAtmSizer(rbId,rbData),0)
1170                XYZ = np.array([[0.,0.,0.] for Ty in rbData['rbTypes']])
1171                for imag,mag in enumerate(rbData['VectMag']):
1172                    XYZ += mag*rbData['rbVect'][imag]
1173                    VectorRBSizer.Add(rbVectMag(rbId,imag,rbData),0)
1174                    VectorRBSizer.Add(rbVectors(rbId,imag,mag,XYZ,rbData),0)
1175                VectorRBSizer.Add((5,5),0)
1176                data['Vector'][rbId]['rbXYZ'] = XYZ       
1177        VectorRBSizer.Layout()   
1178        VectorRBDisplay.SetSizer(VectorRBSizer,True)
1179        Size = VectorRBSizer.GetMinSize()
1180        Size[0] += 40
1181        Size[1] = max(Size[1],450) + 20
1182        VectorRBDisplay.SetSize(Size)
1183        VectorRB.SetScrollbars(10,10,Size[0]/10-4,Size[1]/10-1)
1184        VectorRB.Scroll(0,Scroll)
1185        Size[1] = min(Size[1],450)
1186        G2frame.dataFrame.setSizePosLeft(Size)
1187       
1188    def UpdateResidueRB():
1189        AtInfo = data['Residue']['AtInfo']
1190        refChoice = {}
1191        RefObjs = []
1192
1193        def rbNameSizer(rbId,rbData):
1194
1195            def OnRBName(event):
1196                Obj = event.GetEventObject()
1197                rbId = Indx[Obj.GetId()]
1198                rbData['RBname'] = Obj.GetValue()
1199               
1200            def OnDelRB(event):
1201                Obj = event.GetEventObject()
1202                rbId = Indx[Obj.GetId()]
1203                del data['Residue'][rbId]
1204                data['RBIds']['Residue'].remove(rbId)
1205                wx.CallAfter(UpdateResidueRB)
1206               
1207            def OnPlotRB(event):
1208                Obj = event.GetEventObject()
1209                rbId = Indx[Obj.GetId()]
1210                Obj.SetValue(False)
1211                G2plt.PlotRigidBody(G2frame,'Residue',AtInfo,rbData,plotDefaults)
1212           
1213            nameSizer = wx.BoxSizer(wx.HORIZONTAL)
1214            nameSizer.Add(wx.StaticText(ResidueRBDisplay,-1,'Residue name: '),
1215                0,wx.ALIGN_CENTER_VERTICAL)
1216            RBname = wx.TextCtrl(ResidueRBDisplay,-1,rbData['RBname'])
1217            Indx[RBname.GetId()] = rbId
1218            RBname.Bind(wx.EVT_TEXT_ENTER,OnRBName)
1219            RBname.Bind(wx.EVT_KILL_FOCUS,OnRBName)
1220            nameSizer.Add(RBname,0,wx.ALIGN_CENTER_VERTICAL)
1221            nameSizer.Add((5,0),)
1222            plotRB = wx.CheckBox(ResidueRBDisplay,-1,'Plot?')
1223            Indx[plotRB.GetId()] = rbId
1224            plotRB.Bind(wx.EVT_CHECKBOX,OnPlotRB)
1225            nameSizer.Add(plotRB,0,wx.ALIGN_CENTER_VERTICAL)
1226            nameSizer.Add((5,0),)
1227            if not rbData['useCount']:
1228                delRB = wx.CheckBox(ResidueRBDisplay,-1,'Delete?')
1229                Indx[delRB.GetId()] = rbId
1230                delRB.Bind(wx.EVT_CHECKBOX,OnDelRB)
1231                nameSizer.Add(delRB,0,wx.ALIGN_CENTER_VERTICAL)
1232            return nameSizer
1233           
1234        def rbResidues(rbId,rbData):
1235           
1236            def TypeSelect(event):
1237                AtInfo = data['Residue']['AtInfo']
1238                r,c = event.GetRow(),event.GetCol()
1239                if vecGrid.GetColLabelValue(c) == 'Type':
1240                    PE = G2elemGUI.PickElement(G2frame,oneOnly=True)
1241                    if PE.ShowModal() == wx.ID_OK:
1242                        if PE.Elem != 'None':
1243                            El = PE.Elem.strip().lower().capitalize()
1244                            if El not in AtInfo:
1245                                Info = G2elem.GetAtomInfo(El)
1246                                AtInfo[El] = [Info['Drad']['Color']]
1247                            rbData['rbTypes'][r] = El
1248                            vecGrid.SetCellValue(r,c,El)
1249                    PE.Destroy()
1250
1251            def ChangeCell(event):
1252                r,c =  event.GetRow(),event.GetCol()
1253                if r >= 0 and (0 <= c < 3):
1254                    try:
1255                        val = float(vecGrid.GetCellValue(r,c))
1256                        rbData['rbVect'][imag][r][c] = val
1257                    except ValueError:
1258                        pass
1259                       
1260            def RowSelect(event):
1261                r,c =  event.GetRow(),event.GetCol()
1262                if c < 0:                   #only row clicks
1263                    for vecgrid in resList:
1264                        vecgrid.ClearSelection()
1265                    vecGrid.SelectRow(r,True)
1266
1267            def OnRefSel(event):
1268                Obj = event.GetEventObject()
1269                iref,res,jref = Indx[Obj.GetId()]
1270                sel = Obj.GetValue()
1271                ind = atNames.index(sel)
1272                rbData['rbRef'][iref] = ind
1273                FillRefChoice(rbId,rbData)
1274                for i,ref in enumerate(RefObjs[jref]):
1275                    ref.SetItems([atNames[j] for j in refChoice[rbId][i]])
1276                    ref.SetValue(atNames[rbData['rbRef'][i]])
1277                if not iref:     #origin change
1278                    rbXYZ = rbData['rbXYZ']
1279                    rbXYZ -= rbXYZ[ind]
1280                    res.ClearSelection()
1281                    resTable = res.GetTable()
1282                    for r in range(res.GetNumberRows()):
1283                        row = resTable.GetRowValues(r)
1284                        row[2:4] = rbXYZ[r]
1285                        resTable.SetRowValues(r,row)
1286                    res.ForceRefresh()
1287                    G2plt.PlotRigidBody(G2frame,'Residue',AtInfo,rbData,plotDefaults)
1288               
1289            Types = 2*[wg.GRID_VALUE_STRING,]+3*[wg.GRID_VALUE_FLOAT+':10,5',]
1290            colLabels = ['Name','Type','Cart x','Cart y','Cart z']
1291            table = []
1292            rowLabels = []
1293            for ivec,xyz in enumerate(rbData['rbXYZ']):
1294                table.append([rbData['atNames'][ivec],]+[rbData['rbTypes'][ivec],]+list(xyz))
1295                rowLabels.append(str(ivec))
1296            vecTable = G2gd.Table(table,rowLabels=rowLabels,colLabels=colLabels,types=Types)
1297            vecGrid = G2gd.GSGrid(ResidueRBDisplay)
1298            Indx[vecGrid.GetId()] = rbId
1299            resList.append(vecGrid)
1300            vecGrid.SetTable(vecTable, True)
1301            vecGrid.Bind(wg.EVT_GRID_CELL_CHANGE, ChangeCell)
1302            vecGrid.Bind(wg.EVT_GRID_CELL_LEFT_DCLICK, TypeSelect)
1303            vecGrid.Bind(wg.EVT_GRID_LABEL_LEFT_CLICK, RowSelect)
1304            attr = wx.grid.GridCellAttr()
1305            attr.SetEditor(G2gd.GridFractionEditor(vecGrid))
1306            for c in range(3):
1307                vecGrid.SetColAttr(c, attr)
1308            for row in range(vecTable.GetNumberRows()):
1309                for col in range(5):
1310                    vecGrid.SetCellStyle(row,col,VERY_LIGHT_GREY,True)
1311            vecGrid.SetMargins(0,0)
1312            vecGrid.AutoSizeColumns(False)
1313            vecSizer = wx.BoxSizer()
1314            vecSizer.Add(vecGrid)
1315           
1316            refAtmSizer = wx.BoxSizer(wx.HORIZONTAL)
1317            atNames = rbData['atNames']
1318            rbRef = rbData['rbRef']
1319            if rbData['rbRef'][3] or rbData['useCount']:
1320                refAtmSizer.Add(wx.StaticText(ResidueRBDisplay,-1,
1321                    'Orientation reference atoms A-B-C: %s, %s, %s'%(atNames[rbRef[0]], \
1322                     atNames[rbRef[1]],atNames[rbRef[2]])),0)
1323            else:
1324                refAtmSizer.Add(wx.StaticText(ResidueRBDisplay,-1,
1325                    'Orientation reference atoms A-B-C: '),0,wx.ALIGN_CENTER_VERTICAL)
1326                refObj = [0,0,0]
1327                for i in range(3):
1328                    choices = [atNames[j] for j in refChoice[rbId][i]]
1329                    refSel = wx.ComboBox(ResidueRBDisplay,-1,value='',
1330                        choices=choices,style=wx.CB_READONLY|wx.CB_DROPDOWN)
1331                    refSel.SetValue(atNames[rbRef[i]])
1332                    refSel.Bind(wx.EVT_COMBOBOX, OnRefSel)
1333                    Indx[refSel.GetId()] = [i,vecGrid,len(RefObjs)]
1334                    refObj[i] = refSel
1335                    refAtmSizer.Add(refSel,0,wx.ALIGN_CENTER_VERTICAL)
1336                RefObjs.append(refObj)
1337           
1338            mainSizer = wx.BoxSizer(wx.VERTICAL)
1339            mainSizer.Add(refAtmSizer)
1340            mainSizer.Add(vecSizer)
1341            return mainSizer
1342           
1343        def SeqSizer(angSlide,rbId,iSeq,Seq,atNames):
1344           
1345            def ChangeAngle(event):
1346                Obj = event.GetEventObject()
1347                rbId,Seq = Indx[Obj.GetId()][:2]
1348                val = Seq[2]
1349                try:
1350                    val = float(Obj.GetValue())
1351                    Seq[2] = val
1352                except ValueError:
1353                    pass
1354                Obj.SetValue('%8.2f'%(val))
1355                G2plt.PlotRigidBody(G2frame,'Residue',AtInfo,data['Residue'][rbId],plotDefaults)
1356               
1357            def OnRadBtn(event):
1358                Obj = event.GetEventObject()
1359                Seq,iSeq,angId = Indx[Obj.GetId()]
1360                data['Residue'][rbId]['SelSeq'] = [iSeq,angId]
1361                angSlide.SetValue(int(100*Seq[2]))
1362               
1363            def OnDelBtn(event):
1364                Obj = event.GetEventObject()
1365                rbId,Seq = Indx[Obj.GetId()]
1366                data['Residue'][rbId]['rbSeq'].remove(Seq)       
1367                wx.CallAfter(UpdateResidueRB)
1368           
1369            seqSizer = wx.FlexGridSizer(0,5,2,2)
1370            seqSizer.AddGrowableCol(3,0)
1371            iBeg,iFin,angle,iMove = Seq
1372            ang = wx.TextCtrl(ResidueRBDisplay,-1,'%8.2f'%(angle),size=(50,20))
1373            if not iSeq:
1374                radBt = wx.RadioButton(ResidueRBDisplay,-1,'',style=wx.RB_GROUP)
1375                data['Residue'][rbId]['SelSeq'] = [iSeq,ang.GetId()]
1376            else:
1377                radBt = wx.RadioButton(ResidueRBDisplay,-1,'')
1378            radBt.Bind(wx.EVT_RADIOBUTTON,OnRadBtn)                   
1379            seqSizer.Add(radBt)
1380            delBt = wx.RadioButton(ResidueRBDisplay,-1,'')
1381            delBt.Bind(wx.EVT_RADIOBUTTON,OnDelBtn)
1382            seqSizer.Add(delBt)
1383            bond = wx.TextCtrl(ResidueRBDisplay,-1,'%s %s'%(atNames[iBeg],atNames[iFin]),size=(50,20))
1384            seqSizer.Add(bond,0,wx.ALIGN_CENTER_VERTICAL)
1385            Indx[radBt.GetId()] = [Seq,iSeq,ang.GetId()]
1386            Indx[delBt.GetId()] = [rbId,Seq]
1387            Indx[ang.GetId()] = [rbId,Seq,ang]
1388            ang.Bind(wx.EVT_TEXT_ENTER,ChangeAngle)
1389            ang.Bind(wx.EVT_KILL_FOCUS,ChangeAngle)
1390            seqSizer.Add(ang,0,wx.ALIGN_CENTER_VERTICAL)
1391            atms = ''
1392            for i in iMove:   
1393                atms += ' %s,'%(atNames[i])
1394            moves = wx.TextCtrl(ResidueRBDisplay,-1,atms[:-1],size=(200,20))
1395            seqSizer.Add(moves,1,wx.ALIGN_CENTER_VERTICAL|wx.EXPAND|wx.RIGHT)
1396            return seqSizer
1397           
1398        def SlideSizer():
1399           
1400            def OnSlider(event):
1401                Obj = event.GetEventObject()
1402                rbData = Indx[Obj.GetId()]
1403                iSeq,angId = rbData['SelSeq']
1404                val = float(Obj.GetValue())/100.
1405                rbData['rbSeq'][iSeq][2] = val
1406                Indx[angId][2].SetValue('%8.2f'%(val))
1407                G2plt.PlotRigidBody(G2frame,'Residue',AtInfo,rbData,plotDefaults)
1408           
1409            slideSizer = wx.BoxSizer(wx.HORIZONTAL)
1410            slideSizer.Add(wx.StaticText(ResidueRBDisplay,-1,'Selected torsion angle:'),0)
1411            iSeq,angId = rbData['SelSeq']
1412            angSlide = wx.Slider(ResidueRBDisplay,-1,
1413                int(100*rbData['rbSeq'][iSeq][2]),0,36000,size=(200,20),
1414                style=wx.SL_HORIZONTAL)
1415            angSlide.Bind(wx.EVT_SLIDER, OnSlider)
1416            Indx[angSlide.GetId()] = rbData
1417            slideSizer.Add(angSlide,0)           
1418            return slideSizer,angSlide
1419           
1420        def FillRefChoice(rbId,rbData):
1421            choiceIds = [i for i in range(len(rbData['atNames']))]
1422            for seq in rbData['rbSeq']:
1423                for i in seq[3]:
1424                    try:
1425                        choiceIds.remove(i)
1426                    except ValueError:
1427                        pass
1428            rbRef = rbData['rbRef']
1429            for i in range(3):
1430                try:
1431                    choiceIds.remove(rbRef[i])
1432                except ValueError:
1433                    pass
1434            refChoice[rbId] = [choiceIds[:],choiceIds[:],choiceIds[:]]
1435            for i in range(3):
1436                refChoice[rbId][i].append(rbRef[i])
1437                refChoice[rbId][i].sort()     
1438           
1439        ResidueRB.DestroyChildren()
1440        ResidueRBDisplay = wx.Panel(ResidueRB)
1441        ResidueRBSizer = wx.BoxSizer(wx.VERTICAL)
1442        for rbId in data['RBIds']['Residue']:
1443            rbData = data['Residue'][rbId]
1444            FillRefChoice(rbId,rbData)
1445            ResidueRBSizer.Add(rbNameSizer(rbId,rbData),0)
1446            ResidueRBSizer.Add(rbResidues(rbId,rbData),0)
1447            ResidueRBSizer.Add((5,5),0)
1448            if rbData['rbSeq']:
1449                slideSizer,angSlide = SlideSizer()
1450            if len(rbData['rbSeq']):
1451                ResidueRBSizer.Add(wx.StaticText(ResidueRBDisplay,-1,
1452                    'Sel  Del  Bond             Angle      Riding atoms'),
1453                    0,wx.ALIGN_CENTER_VERTICAL)                       
1454            for iSeq,Seq in enumerate(rbData['rbSeq']):
1455                ResidueRBSizer.Add(SeqSizer(angSlide,rbId,iSeq,Seq,rbData['atNames']))
1456            if rbData['rbSeq']:
1457                ResidueRBSizer.Add(slideSizer,)
1458            ResidueRBSizer.Add(wx.StaticText(ResidueRBDisplay,-1,100*'-'))
1459
1460        ResidueRBSizer.Add((5,25),)
1461        ResidueRBSizer.Layout()   
1462        ResidueRBDisplay.SetSizer(ResidueRBSizer,True)
1463        Size = ResidueRBSizer.GetMinSize()
1464        Size[0] += 40
1465        Size[1] = max(Size[1],450) + 20
1466        ResidueRBDisplay.SetSize(Size)
1467        ResidueRB.SetScrollbars(10,10,Size[0]/10-4,Size[1]/10-1)
1468        Size[1] = min(600,Size[1])
1469        G2frame.dataFrame.setSizePosLeft(Size)
1470       
1471    def SetStatusLine(text):
1472        Status.SetStatusText(text)                                     
1473
1474    if G2frame.dataDisplay:
1475        G2frame.dataDisplay.Destroy()
1476    G2gd.SetDataMenuBar(G2frame,G2frame.dataFrame.RigidBodyMenu)
1477    G2frame.dataFrame.SetLabel('Rigid bodies')
1478    if not G2frame.dataFrame.GetStatusBar():
1479        Status = G2frame.dataFrame.CreateStatusBar()
1480    SetStatusLine('')
1481
1482    G2gd.SetDataMenuBar(G2frame,G2frame.dataFrame.RigidBodyMenu)
1483    G2frame.dataFrame.Bind(wx.EVT_MENU, OnAddRigidBody, id=G2gd.wxID_RIGIDBODYADD)   
1484    G2frame.dataFrame.Bind(wx.EVT_MENU, OnImportRigidBody, id=G2gd.wxID_RIGIDBODYIMPORT)
1485    G2frame.dataFrame.Bind(wx.EVT_MENU, OnDefineTorsSeq, id=G2gd.wxID_RESIDUETORSSEQ)
1486    G2frame.dataDisplay = G2gd.GSNoteBook(parent=G2frame.dataFrame,size=G2frame.dataFrame.GetClientSize())
1487    G2frame.dataDisplay.Bind(wx.aui.EVT_AUINOTEBOOK_PAGE_CHANGED, OnPageChanged)
1488
1489    VectorRB = wx.ScrolledWindow(G2frame.dataDisplay)
1490    G2frame.dataDisplay.AddPage(VectorRB,'Vector rigid bodies')
1491    ResidueRB = wx.ScrolledWindow(G2frame.dataDisplay)
1492    G2frame.dataDisplay.AddPage(ResidueRB,'Residue rigid bodies')
1493    UpdateVectorRB()
1494   
Note: See TracBrowser for help on using the repository browser.