source: trunk/GSASIIconstrGUI.py @ 2434

Last change on this file since 2434 was 2434, checked in by toby, 5 years ago

Fix constraint edit bug in wx3.0; relabel constraint menu items to avoid Mac glitch & make meaning more clear

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