source: trunk/GSASIIconstrGUI.py @ 1657

Last change on this file since 1657 was 1657, checked in by toby, 7 years ago

migrate help from G2grid; set instparms in Powder imports; seach for imports in data path; start on tutorial migration

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Author Revision URL Id
File size: 78.8 KB
Line 
1# -*- coding: utf-8 -*-
2#GSASIIconstrGUI - constraint GUI routines
3########### SVN repository information ###################
4# $Date: 2015-02-16 20:46:06 +0000 (Mon, 16 Feb 2015) $
5# $Author: toby $
6# $Revision: 1657 $
7# $URL: trunk/GSASIIconstrGUI.py $
8# $Id: GSASIIconstrGUI.py 1657 2015-02-16 20:46:06Z 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: 1657 $")
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 = G2gd.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 = G2gd.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 = G2gd.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 = G2gd.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 = G2gd.Table(table,rowLabels=rowLabels,colLabels=colLabels,types=Types)
1473            vecGrid = G2gd.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(G2gd.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()
1504        VectorRBDisplay = wx.Panel(VectorRB)
1505        VectorRBSizer = wx.BoxSizer(wx.VERTICAL)
1506        for rbId in data['RBIds']['Vector']:
1507            if rbId != 'AtInfo':
1508                rbData = data['Vector'][rbId]
1509                FillRefChoice(rbId,rbData)
1510                VectorRBSizer.Add(rbNameSizer(rbId,rbData),0)
1511                VectorRBSizer.Add(rbRefAtmSizer(rbId,rbData),0)
1512                XYZ = np.array([[0.,0.,0.] for Ty in rbData['rbTypes']])
1513                for imag,mag in enumerate(rbData['VectMag']):
1514                    XYZ += mag*rbData['rbVect'][imag]
1515                    VectorRBSizer.Add(rbVectMag(rbId,imag,rbData),0)
1516                    VectorRBSizer.Add(rbVectors(rbId,imag,mag,XYZ,rbData),0)
1517                VectorRBSizer.Add((5,5),0)
1518                data['Vector'][rbId]['rbXYZ'] = XYZ       
1519        VectorRBSizer.Layout()   
1520        VectorRBDisplay.SetSizer(VectorRBSizer,True)
1521        Size = VectorRBSizer.GetMinSize()
1522        Size[0] += 40
1523        Size[1] = max(Size[1],450) + 20
1524        VectorRBDisplay.SetSize(Size)
1525        VectorRB.SetScrollbars(10,10,Size[0]/10-4,Size[1]/10-1)
1526        VectorRB.Scroll(0,Scroll)
1527        Size[1] = min(Size[1],450)
1528        G2frame.dataFrame.setSizePosLeft(Size)
1529       
1530    def UpdateResidueRB():
1531        AtInfo = data['Residue']['AtInfo']
1532        refChoice = {}
1533        RefObjs = []
1534
1535        def rbNameSizer(rbId,rbData):
1536
1537            def OnRBName(event):
1538                Obj = event.GetEventObject()
1539                rbId = Indx[Obj.GetId()]
1540                rbData['RBname'] = Obj.GetValue()
1541               
1542            def OnDelRB(event):
1543                Obj = event.GetEventObject()
1544                rbId = Indx[Obj.GetId()]
1545                del data['Residue'][rbId]
1546                data['RBIds']['Residue'].remove(rbId)
1547                wx.CallAfter(UpdateResidueRB)
1548               
1549            def OnPlotRB(event):
1550                Obj = event.GetEventObject()
1551                rbId = Indx[Obj.GetId()]
1552                Obj.SetValue(False)
1553                G2plt.PlotRigidBody(G2frame,'Residue',AtInfo,rbData,plotDefaults)
1554           
1555            nameSizer = wx.BoxSizer(wx.HORIZONTAL)
1556            nameSizer.Add(wx.StaticText(ResidueRBDisplay,-1,'Residue name: '),
1557                0,wx.ALIGN_CENTER_VERTICAL)
1558            RBname = wx.TextCtrl(ResidueRBDisplay,-1,rbData['RBname'])
1559            Indx[RBname.GetId()] = rbId
1560            RBname.Bind(wx.EVT_TEXT_ENTER,OnRBName)
1561            RBname.Bind(wx.EVT_KILL_FOCUS,OnRBName)
1562            nameSizer.Add(RBname,0,wx.ALIGN_CENTER_VERTICAL)
1563            nameSizer.Add((5,0),)
1564            plotRB = wx.CheckBox(ResidueRBDisplay,-1,'Plot?')
1565            Indx[plotRB.GetId()] = rbId
1566            plotRB.Bind(wx.EVT_CHECKBOX,OnPlotRB)
1567            nameSizer.Add(plotRB,0,wx.ALIGN_CENTER_VERTICAL)
1568            nameSizer.Add((5,0),)
1569            if not rbData['useCount']:
1570                delRB = wx.CheckBox(ResidueRBDisplay,-1,'Delete?')
1571                Indx[delRB.GetId()] = rbId
1572                delRB.Bind(wx.EVT_CHECKBOX,OnDelRB)
1573                nameSizer.Add(delRB,0,wx.ALIGN_CENTER_VERTICAL)
1574            return nameSizer
1575           
1576        def rbResidues(rbId,rbData):
1577           
1578            def TypeSelect(event):
1579                AtInfo = data['Residue']['AtInfo']
1580                r,c = event.GetRow(),event.GetCol()
1581                if vecGrid.GetColLabelValue(c) == 'Type':
1582                    PE = G2elemGUI.PickElement(G2frame,oneOnly=True)
1583                    if PE.ShowModal() == wx.ID_OK:
1584                        if PE.Elem != 'None':
1585                            El = PE.Elem.strip().lower().capitalize()
1586                            if El not in AtInfo:
1587                                Info = G2elem.GetAtomInfo(El)
1588                                AtInfo[El] = [Info['Drad']['Color']]
1589                            rbData['rbTypes'][r] = El
1590                            vecGrid.SetCellValue(r,c,El)
1591                    PE.Destroy()
1592
1593            def ChangeCell(event):
1594                r,c =  event.GetRow(),event.GetCol()
1595                if r >= 0 and (0 <= c < 3):
1596                    try:
1597                        val = float(vecGrid.GetCellValue(r,c))
1598                        rbData['rbVect'][imag][r][c] = val
1599                    except ValueError:
1600                        pass
1601                       
1602            def RowSelect(event):
1603                r,c =  event.GetRow(),event.GetCol()
1604                if c < 0:                   #only row clicks
1605                    for vecgrid in resList:
1606                        vecgrid.ClearSelection()
1607                    vecGrid.SelectRow(r,True)
1608
1609            def OnRefSel(event):
1610                Obj = event.GetEventObject()
1611                iref,res,jref = Indx[Obj.GetId()]
1612                sel = Obj.GetValue()
1613                ind = atNames.index(sel)
1614                rbData['rbRef'][iref] = ind
1615                FillRefChoice(rbId,rbData)
1616                for i,ref in enumerate(RefObjs[jref]):
1617                    ref.SetItems([atNames[j] for j in refChoice[rbId][i]])
1618                    ref.SetValue(atNames[rbData['rbRef'][i]])
1619                if not iref:     #origin change
1620                    rbXYZ = rbData['rbXYZ']
1621                    rbXYZ -= rbXYZ[ind]
1622                    res.ClearSelection()
1623                    resTable = res.GetTable()
1624                    for r in range(res.GetNumberRows()):
1625                        row = resTable.GetRowValues(r)
1626                        row[2:4] = rbXYZ[r]
1627                        resTable.SetRowValues(r,row)
1628                    res.ForceRefresh()
1629                    G2plt.PlotRigidBody(G2frame,'Residue',AtInfo,rbData,plotDefaults)
1630               
1631            Types = 2*[wg.GRID_VALUE_STRING,]+3*[wg.GRID_VALUE_FLOAT+':10,5',]
1632            colLabels = ['Name','Type','Cart x','Cart y','Cart z']
1633            table = []
1634            rowLabels = []
1635            for ivec,xyz in enumerate(rbData['rbXYZ']):
1636                table.append([rbData['atNames'][ivec],]+[rbData['rbTypes'][ivec],]+list(xyz))
1637                rowLabels.append(str(ivec))
1638            vecTable = G2gd.Table(table,rowLabels=rowLabels,colLabels=colLabels,types=Types)
1639            vecGrid = G2gd.GSGrid(ResidueRBDisplay)
1640            Indx[vecGrid.GetId()] = rbId
1641            resList.append(vecGrid)
1642            vecGrid.SetTable(vecTable, True)
1643            vecGrid.Bind(wg.EVT_GRID_CELL_CHANGE, ChangeCell)
1644            vecGrid.Bind(wg.EVT_GRID_CELL_LEFT_DCLICK, TypeSelect)
1645            vecGrid.Bind(wg.EVT_GRID_LABEL_LEFT_CLICK, RowSelect)
1646            attr = wx.grid.GridCellAttr()
1647            attr.SetEditor(G2gd.GridFractionEditor(vecGrid))
1648            for c in range(3):
1649                vecGrid.SetColAttr(c, attr)
1650            for row in range(vecTable.GetNumberRows()):
1651                for col in range(5):
1652                    vecGrid.SetCellStyle(row,col,VERY_LIGHT_GREY,True)
1653            vecGrid.SetMargins(0,0)
1654            vecGrid.AutoSizeColumns(False)
1655            vecSizer = wx.BoxSizer()
1656            vecSizer.Add(vecGrid)
1657           
1658            refAtmSizer = wx.BoxSizer(wx.HORIZONTAL)
1659            atNames = rbData['atNames']
1660            rbRef = rbData['rbRef']
1661            if rbData['rbRef'][3] or rbData['useCount']:
1662                refAtmSizer.Add(wx.StaticText(ResidueRBDisplay,-1,
1663                    'Orientation reference atoms A-B-C: %s, %s, %s'%(atNames[rbRef[0]], \
1664                     atNames[rbRef[1]],atNames[rbRef[2]])),0)
1665            else:
1666                refAtmSizer.Add(wx.StaticText(ResidueRBDisplay,-1,
1667                    'Orientation reference atoms A-B-C: '),0,wx.ALIGN_CENTER_VERTICAL)
1668                refObj = [0,0,0]
1669                for i in range(3):
1670                    choices = [atNames[j] for j in refChoice[rbId][i]]
1671                    refSel = wx.ComboBox(ResidueRBDisplay,-1,value='',
1672                        choices=choices,style=wx.CB_READONLY|wx.CB_DROPDOWN)
1673                    refSel.SetValue(atNames[rbRef[i]])
1674                    refSel.Bind(wx.EVT_COMBOBOX, OnRefSel)
1675                    Indx[refSel.GetId()] = [i,vecGrid,len(RefObjs)]
1676                    refObj[i] = refSel
1677                    refAtmSizer.Add(refSel,0,wx.ALIGN_CENTER_VERTICAL)
1678                RefObjs.append(refObj)
1679           
1680            mainSizer = wx.BoxSizer(wx.VERTICAL)
1681            mainSizer.Add(refAtmSizer)
1682            mainSizer.Add(vecSizer)
1683            return mainSizer
1684           
1685        def SeqSizer(angSlide,rbId,iSeq,Seq,atNames):
1686           
1687            def ChangeAngle(event):
1688                Obj = event.GetEventObject()
1689                rbId,Seq = Indx[Obj.GetId()][:2]
1690                val = Seq[2]
1691                try:
1692                    val = float(Obj.GetValue())
1693                    Seq[2] = val
1694                except ValueError:
1695                    pass
1696                Obj.SetValue('%8.2f'%(val))
1697                G2plt.PlotRigidBody(G2frame,'Residue',AtInfo,data['Residue'][rbId],plotDefaults)
1698               
1699            def OnRadBtn(event):
1700                Obj = event.GetEventObject()
1701                Seq,iSeq,angId = Indx[Obj.GetId()]
1702                data['Residue'][rbId]['SelSeq'] = [iSeq,angId]
1703                angSlide.SetValue(int(100*Seq[2]))
1704               
1705            def OnDelBtn(event):
1706                Obj = event.GetEventObject()
1707                rbId,Seq = Indx[Obj.GetId()]
1708                data['Residue'][rbId]['rbSeq'].remove(Seq)       
1709                wx.CallAfter(UpdateResidueRB)
1710           
1711            seqSizer = wx.FlexGridSizer(0,5,2,2)
1712            seqSizer.AddGrowableCol(3,0)
1713            iBeg,iFin,angle,iMove = Seq
1714            ang = wx.TextCtrl(ResidueRBDisplay,-1,'%8.2f'%(angle),size=(50,20))
1715            if not iSeq:
1716                radBt = wx.RadioButton(ResidueRBDisplay,-1,'',style=wx.RB_GROUP)
1717                data['Residue'][rbId]['SelSeq'] = [iSeq,ang.GetId()]
1718            else:
1719                radBt = wx.RadioButton(ResidueRBDisplay,-1,'')
1720            radBt.Bind(wx.EVT_RADIOBUTTON,OnRadBtn)                   
1721            seqSizer.Add(radBt)
1722            delBt = wx.RadioButton(ResidueRBDisplay,-1,'')
1723            delBt.Bind(wx.EVT_RADIOBUTTON,OnDelBtn)
1724            seqSizer.Add(delBt)
1725            bond = wx.TextCtrl(ResidueRBDisplay,-1,'%s %s'%(atNames[iBeg],atNames[iFin]),size=(50,20))
1726            seqSizer.Add(bond,0,wx.ALIGN_CENTER_VERTICAL)
1727            Indx[radBt.GetId()] = [Seq,iSeq,ang.GetId()]
1728            Indx[delBt.GetId()] = [rbId,Seq]
1729            Indx[ang.GetId()] = [rbId,Seq,ang]
1730            ang.Bind(wx.EVT_TEXT_ENTER,ChangeAngle)
1731            ang.Bind(wx.EVT_KILL_FOCUS,ChangeAngle)
1732            seqSizer.Add(ang,0,wx.ALIGN_CENTER_VERTICAL)
1733            atms = ''
1734            for i in iMove:   
1735                atms += ' %s,'%(atNames[i])
1736            moves = wx.TextCtrl(ResidueRBDisplay,-1,atms[:-1],size=(200,20))
1737            seqSizer.Add(moves,1,wx.ALIGN_CENTER_VERTICAL|wx.EXPAND|wx.RIGHT)
1738            return seqSizer
1739           
1740        def SlideSizer():
1741           
1742            def OnSlider(event):
1743                Obj = event.GetEventObject()
1744                rbData = Indx[Obj.GetId()]
1745                iSeq,angId = rbData['SelSeq']
1746                val = float(Obj.GetValue())/100.
1747                rbData['rbSeq'][iSeq][2] = val
1748                Indx[angId][2].SetValue('%8.2f'%(val))
1749                G2plt.PlotRigidBody(G2frame,'Residue',AtInfo,rbData,plotDefaults)
1750           
1751            slideSizer = wx.BoxSizer(wx.HORIZONTAL)
1752            slideSizer.Add(wx.StaticText(ResidueRBDisplay,-1,'Selected torsion angle:'),0)
1753            iSeq,angId = rbData['SelSeq']
1754            angSlide = wx.Slider(ResidueRBDisplay,-1,
1755                int(100*rbData['rbSeq'][iSeq][2]),0,36000,size=(200,20),
1756                style=wx.SL_HORIZONTAL)
1757            angSlide.Bind(wx.EVT_SLIDER, OnSlider)
1758            Indx[angSlide.GetId()] = rbData
1759            slideSizer.Add(angSlide,0)           
1760            return slideSizer,angSlide
1761           
1762        def FillRefChoice(rbId,rbData):
1763            choiceIds = [i for i in range(len(rbData['atNames']))]
1764            for seq in rbData['rbSeq']:
1765                for i in seq[3]:
1766                    try:
1767                        choiceIds.remove(i)
1768                    except ValueError:
1769                        pass
1770            rbRef = rbData['rbRef']
1771            for i in range(3):
1772                try:
1773                    choiceIds.remove(rbRef[i])
1774                except ValueError:
1775                    pass
1776            refChoice[rbId] = [choiceIds[:],choiceIds[:],choiceIds[:]]
1777            for i in range(3):
1778                refChoice[rbId][i].append(rbRef[i])
1779                refChoice[rbId][i].sort()     
1780           
1781        ResidueRB.DestroyChildren()
1782        ResidueRBDisplay = wx.Panel(ResidueRB)
1783        ResidueRBSizer = wx.BoxSizer(wx.VERTICAL)
1784        for rbId in data['RBIds']['Residue']:
1785            rbData = data['Residue'][rbId]
1786            FillRefChoice(rbId,rbData)
1787            ResidueRBSizer.Add(rbNameSizer(rbId,rbData),0)
1788            ResidueRBSizer.Add(rbResidues(rbId,rbData),0)
1789            ResidueRBSizer.Add((5,5),0)
1790            if rbData['rbSeq']:
1791                slideSizer,angSlide = SlideSizer()
1792            if len(rbData['rbSeq']):
1793                ResidueRBSizer.Add(wx.StaticText(ResidueRBDisplay,-1,
1794                    'Sel  Del  Bond             Angle      Riding atoms'),
1795                    0,wx.ALIGN_CENTER_VERTICAL)                       
1796            for iSeq,Seq in enumerate(rbData['rbSeq']):
1797                ResidueRBSizer.Add(SeqSizer(angSlide,rbId,iSeq,Seq,rbData['atNames']))
1798            if rbData['rbSeq']:
1799                ResidueRBSizer.Add(slideSizer,)
1800            ResidueRBSizer.Add(wx.StaticText(ResidueRBDisplay,-1,100*'-'))
1801
1802        ResidueRBSizer.Add((5,25),)
1803        ResidueRBSizer.Layout()   
1804        ResidueRBDisplay.SetSizer(ResidueRBSizer,True)
1805        Size = ResidueRBSizer.GetMinSize()
1806        Size[0] += 40
1807        Size[1] = max(Size[1],450) + 20
1808        ResidueRBDisplay.SetSize(Size)
1809        ResidueRB.SetScrollbars(10,10,Size[0]/10-4,Size[1]/10-1)
1810        Size[1] = min(600,Size[1])
1811        G2frame.dataFrame.setSizePosLeft(Size)
1812       
1813    def SetStatusLine(text):
1814        Status.SetStatusText(text)                                     
1815
1816    if G2frame.dataDisplay:
1817        G2frame.dataDisplay.Destroy()
1818    G2gd.SetDataMenuBar(G2frame,G2frame.dataFrame.RigidBodyMenu)
1819    G2frame.dataFrame.SetLabel('Rigid bodies')
1820    if not G2frame.dataFrame.GetStatusBar():
1821        Status = G2frame.dataFrame.CreateStatusBar()
1822    SetStatusLine('')
1823
1824    G2frame.dataDisplay = G2gd.GSNoteBook(parent=G2frame.dataFrame,size=G2frame.dataFrame.GetClientSize())
1825
1826    VectorRB = wx.ScrolledWindow(G2frame.dataDisplay)
1827    G2frame.dataDisplay.AddPage(VectorRB,'Vector rigid bodies')
1828    ResidueRB = wx.ScrolledWindow(G2frame.dataDisplay)
1829    G2frame.dataDisplay.AddPage(ResidueRB,'Residue rigid bodies')
1830    UpdateVectorRB()
1831    G2frame.dataDisplay.Bind(wx.aui.EVT_AUINOTEBOOK_PAGE_CHANGED, OnPageChanged)
1832    OnPageChanged(None)
Note: See TracBrowser for help on using the repository browser.