source: trunk/GSASIIconstrGUI.py @ 2506

Last change on this file since 2506 was 2506, checked in by vondreele, 6 years ago

some work on automatic mag constraints
move a dlg.Destroy() in OnSeqPeakFit? & OnIndexPeaks? - get rid of orphan wait cursor & progress dialog
more work on mag moment derivs - still wrong! Other derivs now all OK

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