source: trunk/GSASIIconstrGUI.py @ 2482

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

allow atom constraints between phases

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