source: trunk/GSASIIconstrGUI.py @ 2489

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

also for D11, etc. allow all others to be used in equivalence

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