source: trunk/GSASIIconstrGUI.py @ 1359

Last change on this file since 1359 was 1359, checked in by vondreele, 9 years ago

start allowing reading of multiple HKLF files.
put an IndexAllIds? into UpdateConstraints?
edf image fixes
more places in Pawley d-min
format of penalty fxn chi2
allow one Bank selection set to be used for multiple files
new reader for TOF HKLF2 data - nonworking just now

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