source: trunk/GSASIIconstrGUI.py @ 1933

Last change on this file since 1933 was 1925, checked in by vondreele, 10 years ago

create AddHatomDialog? & work on OnHydAtomAdd?
allow reading of .cor images made at APS 1ID

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