source: trunk/GSASIIconstrGUI.py @ 2488

Last change on this file since 2488 was 2488, checked in by vondreele, 5 years ago

Allow equivalence constraints between A0-A5 on one phase and any of A0-A5 on another

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