source: trunk/GSASIIconstrGUI.py @ 1542

Last change on this file since 1542 was 1542, checked in by vondreele, 8 years ago

put in missing Rigid body binds

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