source: trunk/GSASIIconstrGUI.py @ 2062

Last change on this file since 2062 was 2062, checked in by vondreele, 7 years ago

exclude non variable SS parms from constraint lists
define ZigZag? & Block position modulations; eliminate Sawtooth (it's a variant of ZigZag?)
fix SS names in constraint lists
implement ZigZag? & Block position wave plots
implement ZigZag? & Block atom motion in structure plots
add movie making option (hidden - no file output for it yet)
fix LS I/O for ZigZag? & Block waves

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