source: trunk/GSASIIconstrGUI.py @ 1831

Last change on this file since 1831 was 1831, checked in by toby, 8 years ago

move remaining generic controls to G2ctrls

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