source: trunk/GSASIIconstrGUI.py @ 1900

Last change on this file since 1900 was 1898, checked in by vondreele, 10 years ago

new constraint option - constrain atom variables as a set; constrain sum of frac & equivalence all other parameters - intended for multiple atoms on same site
fix bug in instrument parms flag copy

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Author Revision URL Id
File size: 81.9 KB
Line 
1# -*- coding: utf-8 -*-
2#GSASIIconstrGUI - constraint GUI routines
3########### SVN repository information ###################
4# $Date: 2015-06-19 20:59:22 +0000 (Fri, 19 Jun 2015) $
5# $Author: toby $
6# $Revision: 1898 $
7# $URL: trunk/GSASIIconstrGUI.py $
8# $Id: GSASIIconstrGUI.py 1898 2015-06-19 20:59:22Z 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: 1898 $")
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 OnAddAtomEquiv(event):
680        ''' Add equivalences between all parameters on atoms '''
681        page = G2frame.Page
682        vartype,varList,constrDictEnt = PageSelection(page)
683        title1 = "Setup equivalent atom variables"
684        title2 = "Select additional atoms(s) to be equivalent 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 atoms to make equivalent (only one of the atom variables will be varied when all are set to be varied)"
690        GetAddAtomVars(page,title1,title2,varList,constrDictEnt,'equivalence')
691   
692    def OnAddFunction(event):
693        '''add a Function (new variable) constraint'''
694        page = G2frame.Page
695        vartype,varList,constrDictEnt = PageSelection(page)
696        title1 = "Setup new variable based on "+vartype+" variables"
697        title2 = "Include additional "+vartype+" variable(s) to be included 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 new variable (the new variable will be varied when all included variables are varied)"
703        GetAddVars(page,title1,title2,varList,constrDictEnt,'function')
704                       
705    def OnAddConstraint(event):
706        '''add a constraint equation to the constraints list'''
707        page = G2frame.Page
708        vartype,varList,constrDictEnt = PageSelection(page)
709        title1 = "Setup constraint on "+vartype+" variables"
710        title2 = "Select additional "+vartype+" variable(s) to include in constraint with "
711        if not varList:
712            G2frame.ErrorDialog('No variables','There are no variables of type '+vartype,
713                                parent=G2frame.dataFrame)
714            return
715        legend = "Select variables to include in a constraint equation (the values will be constrainted to equal a specified constant)"
716        GetAddVars(page,title1,title2,varList,constrDictEnt,'constraint')
717
718    def GetAddVars(page,title1,title2,varList,constrDictEnt,constType):
719        '''Get the variables to be added for OnAddEquivalence, OnAddFunction,
720        and OnAddConstraint. Then create and check the constraint.
721        '''
722        #varListlbl = ["("+i+") "+G2obj.fmtVarDescr(i) for i in varList]
723        l2 = l1 = 1
724        for i in varList:
725            l1 = max(l1,len(i))
726            loc,desc = G2obj.VarDescr(i)
727            l2 = max(l2,len(loc))
728        fmt = "{:"+str(l1)+"s} {:"+str(l2)+"s} {:s}"
729        varListlbl = [fmt.format(i,*G2obj.VarDescr(i)) for i in varList]       
730        dlg = G2G.G2SingleChoiceDialog(G2frame.dataFrame,'Select 1st variable:',
731                                      title1,varListlbl,
732                                      monoFont=True,size=(625,400))
733        dlg.CenterOnParent()
734        if dlg.ShowModal() == wx.ID_OK:
735            sel = dlg.GetSelection()
736            FrstVarb = varList[sel]
737            VarObj = G2obj.G2VarObj(FrstVarb)
738            moreVarb = FindEquivVarb(FrstVarb,varList)
739            newcons = SelectVarbs(page,VarObj,moreVarb,title2+FrstVarb,constType)
740            if len(newcons) > 0:
741                if CheckAddedConstraint(newcons):
742                    data[constrDictEnt] += newcons
743        dlg.Destroy()
744        OnPageChanged(None)
745                       
746    def GetAddAtomVars(page,title1,title2,varList,constrDictEnt,constType):
747        '''Get the atom variables to be added for OnAddAtomEquiv. Then create and
748        check the constraints.
749        '''
750        Atoms = {G2obj.VarDescr(i)[0]:[] for i in varList if 'Atom' in G2obj.VarDescr(i)[0]}
751        for item in varList:
752            atName = G2obj.VarDescr(item)[0]
753            if atName in Atoms:
754                Atoms[atName].append(item)
755        AtNames = Atoms.keys()
756        AtNames.sort()
757        dlg = G2G.G2SingleChoiceDialog(G2frame.dataFrame,'Select 1st atom:',
758                                      title1,AtNames,
759                                      monoFont=True,size=(625,400))
760        dlg.CenterOnParent()
761        FrstAtom = ''
762        if dlg.ShowModal() == wx.ID_OK:
763            sel = dlg.GetSelection()
764            FrstAtom = AtNames[sel]
765            AtNames.remove(FrstAtom)
766        dlg.Destroy()
767        if FrstAtom == '':
768            print 'no atom selected'
769            return
770        dlg = G2G.G2MultiChoiceDialog(
771            G2frame.dataFrame,title2+FrstAtom,
772            'Constrain '+str(FrstAtom)+' with...',AtNames,
773            toggle=False,size=(625,400),monoFont=True)
774        if dlg.ShowModal() == wx.ID_OK:
775            Selections = dlg.GetSelections()[:]
776        dlg.Destroy()
777        for name in Atoms[FrstAtom]:
778            newcons = []
779            constr = [[1.0,G2obj.G2VarObj(name)]]
780            pref = name.rsplit(':',1)[0]
781            for sel in Selections:
782                id = Atoms[AtNames[sel]][0].rsplit(':',1)[-1]
783                constr += [[1.0,G2obj.G2VarObj('%s:%s'%(pref,id))]]
784            if 'frac' in pref:
785                newcons = [constr+[1.0,None,'c']]
786            else:
787                newcons = [constr+[None,None,'e']]
788            if len(newcons) > 0:
789                if CheckAddedConstraint(newcons):
790                    data[constrDictEnt] += newcons
791        OnPageChanged(None)
792                       
793    def MakeConstraintsSizer(name,pageDisplay):
794        '''Creates a sizer displaying all of the constraints entered of
795        the specified type.
796
797        :param str name: the type of constraints to be displayed ('HAP',
798          'Hist', 'Phase', or 'Global')
799        :param wx.Panel pageDisplay: parent panel for sizer
800        :returns: wx.Sizer created by method
801        '''
802        constSizer = wx.FlexGridSizer(0,6,0,0)
803        maxlen = 70 # characters before wrapping a constraint
804        for Id,item in enumerate(data[name]):
805            refineflag = False
806            helptext = ""
807            eqString = ['',]
808            if item[-1] == 'h': # Hold on variable
809                constSizer.Add((-1,-1),0)              # blank space for edit button
810                typeString = 'FIXED'
811                var = str(item[0][1])
812                varMean = G2obj.fmtVarDescr(var)
813                eqString[-1] =  var +'   '
814                helptext = "Prevents variable:\n"+ var + " ("+ varMean + ")\nfrom being changed"
815            elif isinstance(item[-1],str): # not true on original-style (2011?) constraints
816                constEdit = wx.Button(pageDisplay,-1,'Edit',style=wx.BU_EXACTFIT)
817                constEdit.Bind(wx.EVT_BUTTON,OnConstEdit)
818                constSizer.Add(constEdit,0,wx.LEFT|wx.RIGHT|wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER,1)            # edit button
819                Indx[constEdit.GetId()] = [Id,name]
820                if item[-1] == 'f':
821                    helptext = "A new variable"
822                    if item[-3]:
823                        helptext += " named "+str(item[-3])
824                    helptext += " is created from a linear combination of the following variables:\n"
825                    for term in item[:-3]:
826                        var = str(term[1])
827                        if len(eqString[-1]) > maxlen:
828                            eqString.append(' ')
829                        m = term[0]
830                        if eqString[-1] != '':
831                            if m >= 0:
832                                eqString[-1] += ' + '
833                            else:
834                                eqString[-1] += ' - '
835                                m = abs(m)
836                        eqString[-1] += '%.3f*%s '%(m,var)
837                        varMean = G2obj.fmtVarDescr(var)
838                        helptext += "\n" + var + " ("+ varMean + ")"
839                    if '_Explain' in data:
840                        if data['_Explain'].get(item[-3]):
841                            helptext += '\n\n'
842                            helptext += data['_Explain'][item[-3]]
843                    # typeString = 'NEWVAR'
844                    # if item[-3]:
845                    #     eqString[-1] += ' = '+item[-3]
846                    # else:
847                    #     eqString[-1] += ' = New Variable'
848                    if item[-3]:
849                        typeString = item[-3] + ' = '
850                    else:
851                        typeString = 'New Variable = '
852                    #print 'refine',item[-2]
853                    refineflag = True
854                elif item[-1] == 'c':
855                    helptext = "The following variables constrained to equal a constant:"
856                    for term in item[:-3]:
857                        var = str(term[1])
858                        if len(eqString[-1]) > maxlen:
859                            eqString.append(' ')
860                        if eqString[-1] != '':
861                            if term[0] > 0:
862                                eqString[-1] += ' + '
863                            else:
864                                eqString[-1] += ' - '
865                        eqString[-1] += '%.3f*%s '%(abs(term[0]),var)
866                        varMean = G2obj.fmtVarDescr(var)
867                        helptext += "\n" + var + " ("+ varMean + ")"
868                    typeString = 'CONST'
869                    eqString[-1] += ' = '+str(item[-3])
870                elif item[-1] == 'e':
871                    helptext = "The following variables are set to be equivalent, noting multipliers:"
872                    for term in item[:-3]:
873                        var = str(term[1])
874                        if term[0] == 0: term[0] = 1.0
875                        if len(eqString[-1]) > maxlen:
876                            eqString.append(' ')
877                        if eqString[-1] == '':
878                            eqString[-1] += var+' '
879                            first = term[0]
880                        else:
881                            eqString[-1] += ' = %.3f*%s '%(first/term[0],var)
882                        varMean = G2obj.fmtVarDescr(var)
883                        helptext += "\n" + var + " ("+ varMean + ")"
884                    typeString = 'EQUIV'
885                else:
886                    print 'Unexpected constraint',item
887               
888            else:
889                print 'Removing old-style constraints'
890                data[name] = []
891                return constSizer
892            constDel = wx.Button(pageDisplay,-1,'Delete',style=wx.BU_EXACTFIT)
893            constDel.Bind(wx.EVT_BUTTON,OnConstDel)
894            Indx[constDel.GetId()] = [Id,name]
895            constSizer.Add(constDel,0,wx.LEFT|wx.RIGHT|wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER,1)             # delete button
896            if helptext:
897                ch = G2G.HelpButton(pageDisplay,helptext)
898                constSizer.Add(ch,0,wx.LEFT|wx.RIGHT|wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER,1)
899            else:
900                constSizer.Add((-1,-1))
901            if refineflag:
902                ch = G2G.G2CheckBox(pageDisplay,'',item,-2)
903                constSizer.Add(ch,0,wx.LEFT|wx.RIGHT|wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER,1)
904            else:
905                constSizer.Add((-1,-1))               
906            constSizer.Add(wx.StaticText(pageDisplay,-1,typeString),
907                           0,wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER|wx.RIGHT|wx.LEFT,3)
908            if len(eqString) > 1:
909                Eq = wx.BoxSizer(wx.VERTICAL)
910                for s in eqString:
911                    Eq.Add(wx.StaticText(pageDisplay,-1,s),0,wx.ALIGN_CENTER_VERTICAL)
912            else:
913                Eq = wx.StaticText(pageDisplay,-1,eqString[0])
914            constSizer.Add(Eq,1,wx.ALIGN_CENTER_VERTICAL)
915        return constSizer
916               
917    def OnConstDel(event):
918        'Delete a constraint'
919        Obj = event.GetEventObject()
920        Id,name = Indx[Obj.GetId()]
921        del(data[name][Id])
922        OnPageChanged(None)       
923       
924    def OnConstEdit(event):
925        '''Called to edit an individual contraint in response to a
926        click on its Edit button
927        '''
928        Obj = event.GetEventObject()
929        Id,name = Indx[Obj.GetId()]
930        if data[name][Id][-1] == 'f':
931            items = data[name][Id][:-3]
932            constType = 'New Variable'
933            if data[name][Id][-3]:
934                varname = data[name][Id][-3]
935            else:
936                varname = ""
937            lbl = 'Enter value for each term in constraint; sum = new variable'
938            dlg = ConstraintDialog(G2frame.dataFrame,constType,lbl,items,
939                                   varname=varname,varyflag=data[name][Id][-2])
940        elif data[name][Id][-1] == 'c':
941            items = data[name][Id][:-3]+[
942                [data[name][Id][-3],'fixed value =']]
943            constType = 'Constraint'
944            lbl = 'Edit value for each term in constant constraint sum'
945            dlg = ConstraintDialog(G2frame.dataFrame,constType,lbl,items)
946        elif data[name][Id][-1] == 'e':
947            items = data[name][Id][:-3]
948            constType = 'Equivalence'
949            lbl = 'The following terms are set to be equal:'
950            dlg = ConstraintDialog(G2frame.dataFrame,constType,lbl,items,'/')
951        else:
952            return
953        try:
954            prev = data[name][Id][:]
955            if dlg.ShowModal() == wx.ID_OK:
956                result = dlg.GetData()
957                for i in range(len(data[name][Id][:-3])):
958                    if type(data[name][Id][i]) is tuple: # fix non-mutable construct
959                        data[name][Id][i] = list(data[name][Id][i])
960                    data[name][Id][i][0] = result[i][0]
961                if data[name][Id][-1] == 'c':
962                    data[name][Id][-3] = str(result[-1][0])
963                elif data[name][Id][-1] == 'f':
964                    # process the variable name to put in global form (::var)
965                    varname = str(dlg.newvar[0]).strip().replace(' ','_')
966                    if varname.startswith('::'):
967                        varname = varname[2:]
968                    varname = varname.replace(':',';')
969                    if varname:
970                        data[name][Id][-3] = varname
971                    else:
972                        data[name][Id][-3] = ''
973                    data[name][Id][-2] = dlg.newvar[1]
974                if not CheckChangedConstraint():
975                    data[name][Id] = prev
976        except:
977            import traceback
978            print traceback.format_exc()
979        finally:
980            dlg.Destroy()           
981        OnPageChanged(None)                     
982   
983    def UpdateConstraintPanel(panel,typ):
984        '''Update the contents of the selected Constraint
985        notebook tab. Called in :func:`OnPageChanged`
986        '''
987        if panel.GetSizer(): panel.GetSizer().Clear(True) # N.B. don't use panel.DestroyChildren()
988        # because it deletes scrollbars on Mac
989        Siz = wx.BoxSizer(wx.VERTICAL)
990        Siz.Add((5,5),0)
991        Siz.Add(MakeConstraintsSizer(typ,panel))
992        panel.SetSizer(Siz,True)
993        Size = Siz.GetMinSize()
994        Size[0] += 40
995        Size[1] = max(Size[1],450) + 20
996        panel.SetSize(Size)
997        panel.SetScrollbars(10,10,Size[0]/10-4,Size[1]/10-1)
998        Size[1] = min(500,Size[1])
999        G2frame.dataFrame.setSizePosLeft(Size)
1000#        G2frame.dataFrame.SetSize((500,250)) # set frame size here
1001
1002    def OnPageChanged(event):
1003        '''Called when a tab is pressed or when a "select tab" menu button is
1004        used (see RaisePage), or to refresh the current tab contents (event=None)
1005        '''
1006        if event:       #page change event!
1007            page = event.GetSelection()
1008        else: # called directly, get current page
1009            page = G2frame.dataDisplay.GetSelection()
1010        oldPage = G2frame.dataDisplay.ChangeSelection(page)
1011        text = G2frame.dataDisplay.GetPageText(page)
1012        G2frame.dataFrame.ConstraintEdit.Enable(G2gd.wxID_EQUIVALANCEATOMS,False)
1013        if text == 'Histogram/Phase constraints':
1014            G2frame.Page = [page,'hap']
1015            UpdateConstraintPanel(HAPConstr,'HAP')
1016        elif text == 'Histogram constraints':
1017            G2frame.Page = [page,'hst']
1018            UpdateConstraintPanel(HistConstr,'Hist')
1019        elif text == 'Phase constraints':
1020            G2frame.Page = [page,'phs']
1021            G2frame.dataFrame.ConstraintEdit.Enable(G2gd.wxID_EQUIVALANCEATOMS,True)
1022            UpdateConstraintPanel(PhaseConstr,'Phase')
1023        elif text == 'Global constraints':
1024            G2frame.Page = [page,'glb']
1025            UpdateConstraintPanel(GlobalConstr,'Global')
1026
1027    def RaisePage(event):
1028        'Respond to a "select tab" menu button'
1029        try:
1030            i = (G2gd.wxID_CONSPHASE,
1031                 G2gd.wxID_CONSHAP,
1032                 G2gd.wxID_CONSHIST,
1033                 G2gd.wxID_CONSGLOBAL).index(event.GetId())
1034            G2frame.dataDisplay.SetSelection(i)
1035            OnPageChanged(None)
1036        except ValueError:
1037            print('Unexpected event in RaisePage')
1038
1039    def SetStatusLine(text):
1040        Status.SetStatusText(text)                                     
1041       
1042    if G2frame.dataDisplay:
1043        G2frame.dataDisplay.Destroy()
1044    G2gd.SetDataMenuBar(G2frame,G2frame.dataFrame.ConstraintMenu)
1045    G2frame.dataFrame.SetLabel('Constraints')
1046    if not G2frame.dataFrame.GetStatusBar():
1047        Status = G2frame.dataFrame.CreateStatusBar()
1048    SetStatusLine('')
1049   
1050    G2gd.SetDataMenuBar(G2frame,G2frame.dataFrame.ConstraintMenu)
1051    G2frame.dataFrame.Bind(wx.EVT_MENU, OnAddConstraint, id=G2gd.wxID_CONSTRAINTADD)
1052    G2frame.dataFrame.Bind(wx.EVT_MENU, OnAddFunction, id=G2gd.wxID_FUNCTADD)
1053    G2frame.dataFrame.Bind(wx.EVT_MENU, OnAddEquivalence, id=G2gd.wxID_EQUIVADD)
1054    G2frame.dataFrame.Bind(wx.EVT_MENU, OnAddHold, id=G2gd.wxID_HOLDADD)
1055    G2frame.dataFrame.Bind(wx.EVT_MENU, OnAddAtomEquiv, id=G2gd.wxID_EQUIVALANCEATOMS)
1056    # tab commands
1057    for id in (G2gd.wxID_CONSPHASE,
1058               G2gd.wxID_CONSHAP,
1059               G2gd.wxID_CONSHIST,
1060               G2gd.wxID_CONSGLOBAL):
1061        G2frame.dataFrame.Bind(wx.EVT_MENU, RaisePage,id=id)
1062
1063    G2frame.dataDisplay = G2G.GSNoteBook(parent=G2frame.dataFrame)
1064    # note that order of pages is hard-coded in RaisePage
1065    PhaseConstr = wx.ScrolledWindow(G2frame.dataDisplay)
1066    G2frame.dataDisplay.AddPage(PhaseConstr,'Phase constraints')
1067    HAPConstr = wx.ScrolledWindow(G2frame.dataDisplay)
1068    G2frame.dataDisplay.AddPage(HAPConstr,'Histogram/Phase constraints')
1069    HistConstr = wx.ScrolledWindow(G2frame.dataDisplay)
1070    G2frame.dataDisplay.AddPage(HistConstr,'Histogram constraints')
1071    GlobalConstr = wx.ScrolledWindow(G2frame.dataDisplay)
1072    G2frame.dataDisplay.AddPage(GlobalConstr,'Global constraints')
1073    OnPageChanged(None) # show initial page
1074    G2frame.dataDisplay.Bind(wx.aui.EVT_AUINOTEBOOK_PAGE_CHANGED, OnPageChanged)
1075    # validate all the constrants -- should not see any errors here normally
1076    allcons = []
1077    for key in data:
1078        if key.startswith('_'): continue
1079        allcons += data[key]
1080    if not len(allcons): return
1081    G2mv.InitVars()   
1082    constDictList,fixedList,ignored = G2stIO.ProcessConstraints(allcons)
1083    errmsg, warnmsg = G2mv.CheckConstraints('',constDictList,fixedList)
1084    if errmsg:
1085        G2frame.ErrorDialog('Constraint Error','Error in constraints:\n'+errmsg,
1086            parent=G2frame.dataFrame)
1087    elif warnmsg:
1088        print 'Unexpected contraint warning:\n',warnmsg
1089       
1090################################################################################
1091#### Rigid bodies
1092################################################################################
1093
1094def UpdateRigidBodies(G2frame,data):
1095    '''Called when Rigid bodies tree item is selected.
1096    Displays the rigid bodies in the data window
1097    '''
1098    if not data.get('RBIds') or not data:
1099        data.update({'Vector':{'AtInfo':{}},'Residue':{'AtInfo':{}},
1100            'RBIds':{'Vector':[],'Residue':[]}})       #empty/bad dict - fill it
1101           
1102    global resList
1103    Indx = {}
1104    resList = []
1105    plotDefaults = {'oldxy':[0.,0.],'Quaternion':[0.,0.,0.,1.],'cameraPos':30.,'viewDir':[0,0,1],}
1106   
1107    def OnPageChanged(event):
1108        global resList
1109        resList = []
1110        if event:       #page change event!
1111            page = event.GetSelection()
1112        else:
1113            page = G2frame.dataDisplay.GetSelection()
1114        oldPage = G2frame.dataDisplay.ChangeSelection(page)
1115        text = G2frame.dataDisplay.GetPageText(page)
1116        if text == 'Vector rigid bodies':
1117            G2gd.SetDataMenuBar(G2frame,G2frame.dataFrame.VectorBodyMenu)
1118            G2frame.dataFrame.Bind(wx.EVT_MENU, AddVectorRB, id=G2gd.wxID_VECTORBODYADD)
1119            G2frame.Page = [page,'vrb']
1120            UpdateVectorRB()
1121        elif text == 'Residue rigid bodies':
1122            G2gd.SetDataMenuBar(G2frame,G2frame.dataFrame.RigidBodyMenu)
1123            G2frame.dataFrame.Bind(wx.EVT_MENU, AddResidueRB, id=G2gd.wxID_RIGIDBODYADD)
1124            G2frame.dataFrame.Bind(wx.EVT_MENU, OnImportRigidBody, id=G2gd.wxID_RIGIDBODYIMPORT)
1125            G2frame.dataFrame.Bind(wx.EVT_MENU, OnDefineTorsSeq, id=G2gd.wxID_RESIDUETORSSEQ)
1126            G2frame.Page = [page,'rrb']
1127            UpdateResidueRB()
1128           
1129    def getMacroFile(macName):
1130        defDir = os.path.join(os.path.split(__file__)[0],'GSASIImacros')
1131        dlg = wx.FileDialog(G2frame,message='Choose '+macName+' rigid body macro file',
1132            defaultDir=defDir,defaultFile="",wildcard="GSAS-II macro file (*.mac)|*.mac",
1133            style=wx.OPEN | wx.CHANGE_DIR)
1134        try:
1135            if dlg.ShowModal() == wx.ID_OK:
1136                macfile = dlg.GetPath()
1137                macro = open(macfile,'Ur')
1138                head = macro.readline()
1139                if macName not in head:
1140                    print head
1141                    print '**** ERROR - wrong restraint macro file selected, try again ****'
1142                    macro = []
1143            else: # cancel was pressed
1144                macro = []
1145        finally:
1146            dlg.Destroy()
1147        return macro        #advanced past 1st line
1148       
1149    def getTextFile():
1150        defDir = os.path.join(os.path.split(__file__)[0],'GSASIImacros')
1151        dlg = wx.FileDialog(G2frame,'Choose rigid body text file', '.', '',
1152            "GSAS-II text file (*.txt)|*.txt|XYZ file (*.xyz)|*.xyz|"
1153            "Sybyl mol2 file (*.mol2)|*.mol2|PDB file (*.pdb;*.ent)|*.pdb;*.ent",
1154            wx.OPEN | wx.CHANGE_DIR)
1155        try:
1156            if dlg.ShowModal() == wx.ID_OK:
1157                txtfile = dlg.GetPath()
1158                ext = os.path.splitext(txtfile)[1]
1159                text = open(txtfile,'Ur')
1160            else: # cancel was pressed
1161                ext = ''
1162                text = []
1163        finally:
1164            dlg.Destroy()
1165        if 'ent' in ext:
1166            ext = '.pdb'
1167        return text,ext.lower()
1168       
1169    def OnImportRigidBody(event):
1170        page = G2frame.dataDisplay.GetSelection()
1171        if 'Vector' in G2frame.dataDisplay.GetPageText(page):
1172            pass
1173        elif 'Residue' in G2frame.dataDisplay.GetPageText(page):
1174            ImportResidueRB()
1175           
1176    def AddVectorRB(event):
1177        AtInfo = data['Vector']['AtInfo']
1178        dlg = MultiIntegerDialog(G2frame.dataDisplay,'New Rigid Body',['No. atoms','No. translations'],[1,1])
1179        if dlg.ShowModal() == wx.ID_OK:
1180            nAtoms,nTrans = dlg.GetValues()
1181            vectorRB = data['Vector']
1182            rbId = ran.randint(0,sys.maxint)
1183            vecMag = [1.0 for i in range(nTrans)]
1184            vecRef = [False for i in range(nTrans)]
1185            vecVal = [np.zeros((nAtoms,3)) for j in range(nTrans)]
1186            rbTypes = ['C' for i in range(nAtoms)]
1187            Info = G2elem.GetAtomInfo('C')
1188            AtInfo['C'] = [Info['Drad'],Info['Color']]
1189            data['Vector'][rbId] = {'RBname':'UNKRB','VectMag':vecMag,'rbXYZ':np.zeros((nAtoms,3)),
1190                'rbRef':[0,1,2,False],'VectRef':vecRef,'rbTypes':rbTypes,'rbVect':vecVal,'useCount':0}
1191            data['RBIds']['Vector'].append(rbId)
1192        dlg.Destroy()
1193        UpdateVectorRB()
1194       
1195    def AddResidueRB(event):
1196        AtInfo = data['Residue']['AtInfo']
1197        macro = getMacroFile('rigid body')
1198        if not macro:
1199            return
1200        macStr = macro.readline()
1201        while macStr:
1202            items = macStr.split()
1203            if 'I' == items[0]:
1204                rbId = ran.randint(0,sys.maxint)
1205                rbName = items[1]
1206                rbTypes = []
1207                rbXYZ = []
1208                rbSeq = []
1209                atNames = []
1210                nAtms,nSeq,nOrig,mRef,nRef = [int(items[i]) for i in [2,3,4,5,6]]
1211                for iAtm in range(nAtms):
1212                    macStr = macro.readline().split()
1213                    atName = macStr[0]
1214                    atType = macStr[1]
1215                    atNames.append(atName)
1216                    rbXYZ.append([float(macStr[i]) for i in [2,3,4]])
1217                    rbTypes.append(atType)
1218                    if atType not in AtInfo:
1219                        Info = G2elem.GetAtomInfo(atType)
1220                        AtInfo[atType] = [Info['Drad'],Info['Color']]
1221                rbXYZ = np.array(rbXYZ)-np.array(rbXYZ[nOrig-1])
1222                for iSeq in range(nSeq):
1223                    macStr = macro.readline().split()
1224                    mSeq = int(macStr[0])
1225                    for jSeq in range(mSeq):
1226                        macStr = macro.readline().split()
1227                        iBeg = int(macStr[0])-1
1228                        iFin = int(macStr[1])-1
1229                        angle = 0.0
1230                        nMove = int(macStr[2])
1231                        iMove = [int(macStr[i])-1 for i in range(3,nMove+3)]
1232                        rbSeq.append([iBeg,iFin,angle,iMove])
1233                data['Residue'][rbId] = {'RBname':rbName,'rbXYZ':rbXYZ,'rbTypes':rbTypes,
1234                    'atNames':atNames,'rbRef':[nOrig-1,mRef-1,nRef-1,True],'rbSeq':rbSeq,
1235                    'SelSeq':[0,0],'useCount':0}
1236                data['RBIds']['Residue'].append(rbId)
1237                print 'Rigid body '+rbName+' added'
1238            macStr = macro.readline()
1239        macro.close()
1240        UpdateResidueRB()
1241       
1242    def ImportResidueRB():
1243        AtInfo = data['Residue']['AtInfo']
1244        text,ext = getTextFile()
1245        if not text:
1246            return
1247        rbId = ran.randint(0,sys.maxint)
1248        rbTypes = []
1249        rbXYZ = []
1250        rbSeq = []
1251        atNames = []
1252        txtStr = text.readline()
1253        if 'xyz' in ext:
1254            txtStr = text.readline()
1255            txtStr = text.readline()
1256        elif 'mol2' in ext:
1257            while 'ATOM' not in txtStr:
1258                txtStr = text.readline()
1259            txtStr = text.readline()
1260        elif 'pdb' in ext:
1261            while 'ATOM' not in txtStr[:6] and 'HETATM' not in txtStr[:6]:
1262                txtStr = text.readline()
1263                #print txtStr
1264        items = txtStr.split()
1265        while len(items):
1266            if 'txt' in ext:
1267                atName = items[0]
1268                atType = items[1]
1269                rbXYZ.append([float(items[i]) for i in [2,3,4]])
1270            elif 'xyz' in ext:
1271                atType = items[0]
1272                rbXYZ.append([float(items[i]) for i in [1,2,3]])
1273                atName = atType+str(len(rbXYZ))
1274            elif 'mol2' in ext:
1275                atType = items[1]
1276                atName = items[1]+items[0]
1277                rbXYZ.append([float(items[i]) for i in [2,3,4]])
1278            elif 'pdb' in ext:
1279                atType = items[-1]
1280                atName = items[2]
1281                xyz = txtStr[30:55].split()                   
1282                rbXYZ.append([float(x) for x in xyz])
1283            atNames.append(atName)
1284            rbTypes.append(atType)
1285            if atType not in AtInfo:
1286                Info = G2elem.GetAtomInfo(atType)
1287                AtInfo[atType] = [Info['Drad'],Info['Color']]
1288            txtStr = text.readline()
1289            if 'mol2' in ext and 'BOND' in txtStr:
1290                break
1291            if 'pdb' in ext and ('ATOM' not in txtStr[:6] and 'HETATM' not in txtStr[:6]):
1292                break
1293            items = txtStr.split()
1294        rbXYZ = np.array(rbXYZ)-np.array(rbXYZ[0])
1295        data['Residue'][rbId] = {'RBname':'UNKRB','rbXYZ':rbXYZ,'rbTypes':rbTypes,
1296            'atNames':atNames,'rbRef':[0,1,2,False],'rbSeq':[],'SelSeq':[0,0],'useCount':0}
1297        data['RBIds']['Residue'].append(rbId)
1298        print 'Rigid body UNKRB added'
1299        text.close()
1300        UpdateResidueRB()
1301       
1302    def FindNeighbors(Orig,XYZ,atTypes,atNames,AtInfo):
1303        Radii = []
1304        for Atype in atTypes:
1305            Radii.append(AtInfo[Atype][0])
1306        Radii = np.array(Radii)
1307        Neigh = []
1308        Dx = XYZ-XYZ[Orig]
1309        dist = np.sqrt(np.sum(Dx**2,axis=1))
1310        sumR = Radii[Orig]+Radii
1311        IndB = ma.nonzero(ma.masked_greater(dist-0.85*sumR,0.))
1312        for j in IndB[0]:
1313            if j != Orig:
1314                Neigh.append(atNames[j])
1315        return Neigh
1316       
1317    def FindAllNeighbors(XYZ,atTypes,atNames,AtInfo):
1318        NeighDict = {}
1319        for iat,xyz in enumerate(atNames):
1320            NeighDict[atNames[iat]] = FindNeighbors(iat,XYZ,atTypes,atNames,AtInfo)
1321        return NeighDict
1322       
1323    def FindRiding(Orig,Pivot,NeighDict):
1324        riding = [Orig,Pivot]
1325        iAdd = 1
1326        new = True
1327        while new:
1328            newAtms = NeighDict[riding[iAdd]]
1329            for At in newAtms:
1330                new = False
1331                if At not in riding:
1332                    riding.append(At)
1333                    new = True
1334            iAdd += 1
1335            if iAdd < len(riding):
1336                new = True
1337        return riding[2:]
1338                       
1339    def OnDefineTorsSeq(event):
1340        rbKeys = data['Residue'].keys()
1341        rbKeys.remove('AtInfo')
1342        rbNames = [data['Residue'][k]['RBname'] for k in rbKeys]
1343        rbIds = dict(zip(rbNames,rbKeys))
1344        rbNames.sort()
1345        rbId = 0
1346        if len(rbNames) > 1:
1347            dlg = wx.SingleChoiceDialog(G2frame,'Select rigid body for torsion sequence','Torsion sequence',rbNames)
1348            if dlg.ShowModal() == wx.ID_OK:
1349                sel = dlg.GetSelection()
1350                rbId = rbIds[rbNames[sel]]
1351                rbData = data['Residue'][rbId]
1352            dlg.Destroy()
1353        else:
1354            rbId = rbIds[rbNames[0]]
1355            rbData = data['Residue'][rbId]
1356        if not len(rbData):
1357            return
1358        atNames = rbData['atNames']
1359        AtInfo = data['Residue']['AtInfo']
1360        atTypes = rbData['rbTypes']
1361        XYZ = rbData['rbXYZ']
1362        neighDict = FindAllNeighbors(XYZ,atTypes,atNames,AtInfo)
1363        TargList = []           
1364        dlg = wx.SingleChoiceDialog(G2frame,'Select origin atom for torsion sequence','Origin atom',rbData['atNames'])
1365        if dlg.ShowModal() == wx.ID_OK:
1366            Orig = dlg.GetSelection()
1367            xyz = XYZ[Orig]
1368            TargList = neighDict[atNames[Orig]]
1369        dlg.Destroy()
1370        if not len(TargList):
1371            return
1372        dlg = wx.SingleChoiceDialog(G2frame,'Select pivot atom for torsion sequence','Pivot atom',TargList)
1373        if dlg.ShowModal() == wx.ID_OK:
1374            Piv = atNames.index(TargList[dlg.GetSelection()])
1375            riding = FindRiding(atNames[Orig],atNames[Piv],neighDict)
1376            Riding = []
1377            for atm in riding:
1378                Riding.append(atNames.index(atm))
1379            rbData['rbSeq'].append([Orig,Piv,0.0,Riding])           
1380        dlg.Destroy()
1381        UpdateResidueRB()
1382
1383    def UpdateVectorRB(Scroll=0):
1384        AtInfo = data['Vector']['AtInfo']
1385        refChoice = {}
1386        SetStatusLine(' You may use e.g. "c60" or "s60" for a vector entry')
1387        def rbNameSizer(rbId,rbData):
1388
1389            def OnRBName(event):
1390                Obj = event.GetEventObject()
1391                rbId = Indx[Obj.GetId()]
1392                rbData['RBname'] = Obj.GetValue()
1393               
1394            def OnDelRB(event):
1395                Obj = event.GetEventObject()
1396                rbId = Indx[Obj.GetId()]
1397                del data['Vector'][rbId]
1398                data['RBIds']['Vector'].remove(rbId)
1399                rbData['useCount'] -= 1
1400                wx.CallAfter(UpdateVectorRB)
1401               
1402            def OnPlotRB(event):
1403                Obj = event.GetEventObject()
1404                rbId = Indx[Obj.GetId()]
1405                Obj.SetValue(False)
1406                G2plt.PlotRigidBody(G2frame,'Vector',AtInfo,rbData,plotDefaults)
1407           
1408            nameSizer = wx.BoxSizer(wx.HORIZONTAL)
1409            nameSizer.Add(wx.StaticText(VectorRBDisplay,-1,'Rigid body name: '),
1410                0,wx.ALIGN_CENTER_VERTICAL)
1411            RBname = wx.TextCtrl(VectorRBDisplay,-1,rbData['RBname'])
1412            Indx[RBname.GetId()] = rbId
1413            RBname.Bind(wx.EVT_TEXT_ENTER,OnRBName)
1414            RBname.Bind(wx.EVT_KILL_FOCUS,OnRBName)
1415            nameSizer.Add(RBname,0,wx.ALIGN_CENTER_VERTICAL)
1416            nameSizer.Add((5,0),)
1417            plotRB = wx.CheckBox(VectorRBDisplay,-1,'Plot?')
1418            Indx[plotRB.GetId()] = rbId
1419            plotRB.Bind(wx.EVT_CHECKBOX,OnPlotRB)
1420            nameSizer.Add(plotRB,0,wx.ALIGN_CENTER_VERTICAL)
1421            nameSizer.Add((5,0),)
1422            if not rbData['useCount']:
1423                delRB = wx.CheckBox(VectorRBDisplay,-1,'Delete?')
1424                Indx[delRB.GetId()] = rbId
1425                delRB.Bind(wx.EVT_CHECKBOX,OnDelRB)
1426                nameSizer.Add(delRB,0,wx.ALIGN_CENTER_VERTICAL)
1427            return nameSizer
1428           
1429        def rbRefAtmSizer(rbId,rbData):
1430           
1431            def OnRefSel(event):
1432                Obj = event.GetEventObject()
1433                iref = Indx[Obj.GetId()]
1434                sel = Obj.GetValue()
1435                rbData['rbRef'][iref] = atNames.index(sel)
1436                FillRefChoice(rbId,rbData)
1437           
1438            refAtmSizer = wx.BoxSizer(wx.HORIZONTAL)
1439            atNames = [name+str(i) for i,name in enumerate(rbData['rbTypes'])]
1440            rbRef = rbData.get('rbRef',[0,1,2,False])
1441            rbData['rbRef'] = rbRef
1442            if rbData['useCount']:
1443                refAtmSizer.Add(wx.StaticText(VectorRBDisplay,-1,
1444                    'Orientation reference atoms A-B-C: %s, %s, %s'%(atNames[rbRef[0]], \
1445                     atNames[rbRef[1]],atNames[rbRef[2]])),0)
1446            else:
1447                refAtmSizer.Add(wx.StaticText(VectorRBDisplay,-1,
1448                    'Orientation reference atoms A-B-C: '),0,wx.ALIGN_CENTER_VERTICAL)
1449                for i in range(3):
1450                    choices = [atNames[j] for j in refChoice[rbId][i]]
1451                    refSel = wx.ComboBox(VectorRBDisplay,-1,value='',
1452                        choices=choices,style=wx.CB_READONLY|wx.CB_DROPDOWN)
1453                    refSel.SetValue(atNames[rbRef[i]])
1454                    refSel.Bind(wx.EVT_COMBOBOX, OnRefSel)
1455                    Indx[refSel.GetId()] = i
1456                    refAtmSizer.Add(refSel,0,wx.ALIGN_CENTER_VERTICAL)
1457            return refAtmSizer
1458                       
1459        def rbVectMag(rbId,imag,rbData):
1460           
1461            def OnRBVectorMag(event):
1462                Obj = event.GetEventObject()
1463                rbId,imag = Indx[Obj.GetId()]
1464                try:
1465                    val = float(Obj.GetValue())
1466                    if val <= 0.:
1467                        raise ValueError
1468                    rbData['VectMag'][imag] = val
1469                except ValueError:
1470                    pass
1471                Obj.SetValue('%8.4f'%(val))
1472                wx.CallAfter(UpdateVectorRB,VectorRB.GetScrollPos(wx.VERTICAL))
1473                G2plt.PlotRigidBody(G2frame,'Vector',AtInfo,data['Vector'][rbId],plotDefaults)
1474               
1475            def OnRBVectorRef(event):
1476                Obj = event.GetEventObject()
1477                rbId,imag = Indx[Obj.GetId()]
1478                rbData['VectRef'][imag] = Obj.GetValue()
1479                       
1480            magSizer = wx.wx.BoxSizer(wx.HORIZONTAL)
1481            magSizer.Add(wx.StaticText(VectorRBDisplay,-1,'Translation magnitude: '),
1482                0,wx.ALIGN_CENTER_VERTICAL)
1483            magValue = wx.TextCtrl(VectorRBDisplay,-1,'%8.4f'%(rbData['VectMag'][imag]))
1484            Indx[magValue.GetId()] = [rbId,imag]
1485            magValue.Bind(wx.EVT_TEXT_ENTER,OnRBVectorMag)
1486            magValue.Bind(wx.EVT_KILL_FOCUS,OnRBVectorMag)
1487            magSizer.Add(magValue,0,wx.ALIGN_CENTER_VERTICAL)
1488            magSizer.Add((5,0),)
1489            magref = wx.CheckBox(VectorRBDisplay,-1,label=' Refine?') 
1490            magref.SetValue(rbData['VectRef'][imag])
1491            magref.Bind(wx.EVT_CHECKBOX,OnRBVectorRef)
1492            Indx[magref.GetId()] = [rbId,imag]
1493            magSizer.Add(magref,0,wx.ALIGN_CENTER_VERTICAL)
1494            return magSizer
1495           
1496        def rbVectors(rbId,imag,mag,XYZ,rbData):
1497
1498            def TypeSelect(event):
1499                Obj = event.GetEventObject()
1500                AtInfo = data['Vector']['AtInfo']
1501                r,c = event.GetRow(),event.GetCol()
1502                if vecGrid.GetColLabelValue(c) == 'Type':
1503                    PE = G2elemGUI.PickElement(G2frame,oneOnly=True)
1504                    if PE.ShowModal() == wx.ID_OK:
1505                        if PE.Elem != 'None':
1506                            El = PE.Elem.strip().lower().capitalize()
1507                            if El not in AtInfo:
1508                                Info = G2elem.GetAtomInfo(El)
1509                                AtInfo[El] = [Info['Drad'],Info['Color']]
1510                            rbData['rbTypes'][r] = El
1511                            vecGrid.SetCellValue(r,c,El)
1512                    PE.Destroy()
1513                wx.CallAfter(UpdateVectorRB,VectorRB.GetScrollPos(wx.VERTICAL))
1514
1515            def ChangeCell(event):
1516                Obj = event.GetEventObject()
1517                r,c =  event.GetRow(),event.GetCol()
1518                if r >= 0 and (0 <= c < 3):
1519                    try:
1520                        val = float(vecGrid.GetCellValue(r,c))
1521                        rbData['rbVect'][imag][r][c] = val
1522                    except ValueError:
1523                        pass
1524                G2plt.PlotRigidBody(G2frame,'Vector',AtInfo,data['Vector'][rbId],plotDefaults)
1525                wx.CallAfter(UpdateVectorRB,VectorRB.GetScrollPos(wx.VERTICAL))
1526
1527            vecSizer = wx.BoxSizer()
1528            Types = 3*[wg.GRID_VALUE_FLOAT+':10,5',]+[wg.GRID_VALUE_STRING,]+3*[wg.GRID_VALUE_FLOAT+':10,5',]
1529            colLabels = ['Vector x','Vector y','Vector z','Type','Cart x','Cart y','Cart z']
1530            table = []
1531            rowLabels = []
1532            for ivec,xyz in enumerate(rbData['rbVect'][imag]):
1533                table.append(list(xyz)+[rbData['rbTypes'][ivec],]+list(XYZ[ivec]))
1534                rowLabels.append(str(ivec))
1535            vecTable = G2G.Table(table,rowLabels=rowLabels,colLabels=colLabels,types=Types)
1536            vecGrid = G2G.GSGrid(VectorRBDisplay)
1537            vecGrid.SetTable(vecTable, True)
1538            vecGrid.Bind(wg.EVT_GRID_CELL_CHANGE, ChangeCell)
1539            if not imag:
1540                vecGrid.Bind(wg.EVT_GRID_CELL_LEFT_DCLICK, TypeSelect)
1541            attr = wx.grid.GridCellAttr()
1542            attr.SetEditor(G2G.GridFractionEditor(vecGrid))
1543            for c in range(3):
1544                vecGrid.SetColAttr(c, attr)
1545            for row in range(vecTable.GetNumberRows()):
1546                if imag:
1547                    vecGrid.SetCellStyle(row,3,VERY_LIGHT_GREY,True)                   
1548                for col in [4,5,6]:
1549                    vecGrid.SetCellStyle(row,col,VERY_LIGHT_GREY,True)
1550            vecGrid.SetMargins(0,0)
1551            vecGrid.AutoSizeColumns(False)
1552            vecSizer.Add(vecGrid)
1553            return vecSizer
1554       
1555        def FillRefChoice(rbId,rbData):
1556            choiceIds = [i for i in range(len(rbData['rbTypes']))]
1557           
1558            rbRef = rbData.get('rbRef',[-1,-1,-1,False])
1559            for i in range(3):
1560                choiceIds.remove(rbRef[i])
1561            refChoice[rbId] = [choiceIds[:],choiceIds[:],choiceIds[:]]
1562            for i in range(3):
1563                refChoice[rbId][i].append(rbRef[i])
1564                refChoice[rbId][i].sort()     
1565           
1566        #VectorRB.DestroyChildren() # bad, deletes scrollbars on Mac!
1567        if VectorRB.GetSizer():
1568            VectorRB.GetSizer().Clear(True)
1569        VectorRBDisplay = wx.Panel(VectorRB)
1570        VectorRBSizer = wx.BoxSizer(wx.VERTICAL)
1571        for rbId in data['RBIds']['Vector']:
1572            if rbId != 'AtInfo':
1573                rbData = data['Vector'][rbId]
1574                FillRefChoice(rbId,rbData)
1575                VectorRBSizer.Add(rbNameSizer(rbId,rbData),0)
1576                VectorRBSizer.Add(rbRefAtmSizer(rbId,rbData),0)
1577                XYZ = np.array([[0.,0.,0.] for Ty in rbData['rbTypes']])
1578                for imag,mag in enumerate(rbData['VectMag']):
1579                    XYZ += mag*rbData['rbVect'][imag]
1580                    VectorRBSizer.Add(rbVectMag(rbId,imag,rbData),0)
1581                    VectorRBSizer.Add(rbVectors(rbId,imag,mag,XYZ,rbData),0)
1582                VectorRBSizer.Add((5,5),0)
1583                data['Vector'][rbId]['rbXYZ'] = XYZ       
1584        VectorRBSizer.Layout()   
1585        VectorRBDisplay.SetSizer(VectorRBSizer,True)
1586        Size = VectorRBSizer.GetMinSize()
1587        Size[0] += 40
1588        Size[1] = max(Size[1],450) + 20
1589        VectorRBDisplay.SetSize(Size)
1590        VectorRB.SetScrollbars(10,10,Size[0]/10-4,Size[1]/10-1)
1591        VectorRB.Scroll(0,Scroll)
1592        Size[1] = min(Size[1],450)
1593        G2frame.dataFrame.setSizePosLeft(Size)
1594       
1595    def UpdateResidueRB():
1596        AtInfo = data['Residue']['AtInfo']
1597        refChoice = {}
1598        RefObjs = []
1599
1600        def rbNameSizer(rbId,rbData):
1601
1602            def OnRBName(event):
1603                Obj = event.GetEventObject()
1604                rbId = Indx[Obj.GetId()]
1605                rbData['RBname'] = Obj.GetValue()
1606               
1607            def OnDelRB(event):
1608                Obj = event.GetEventObject()
1609                rbId = Indx[Obj.GetId()]
1610                del data['Residue'][rbId]
1611                data['RBIds']['Residue'].remove(rbId)
1612                wx.CallAfter(UpdateResidueRB)
1613               
1614            def OnPlotRB(event):
1615                Obj = event.GetEventObject()
1616                rbId = Indx[Obj.GetId()]
1617                Obj.SetValue(False)
1618                G2plt.PlotRigidBody(G2frame,'Residue',AtInfo,rbData,plotDefaults)
1619           
1620            nameSizer = wx.BoxSizer(wx.HORIZONTAL)
1621            nameSizer.Add(wx.StaticText(ResidueRBDisplay,-1,'Residue name: '),
1622                0,wx.ALIGN_CENTER_VERTICAL)
1623            RBname = wx.TextCtrl(ResidueRBDisplay,-1,rbData['RBname'])
1624            Indx[RBname.GetId()] = rbId
1625            RBname.Bind(wx.EVT_TEXT_ENTER,OnRBName)
1626            RBname.Bind(wx.EVT_KILL_FOCUS,OnRBName)
1627            nameSizer.Add(RBname,0,wx.ALIGN_CENTER_VERTICAL)
1628            nameSizer.Add((5,0),)
1629            plotRB = wx.CheckBox(ResidueRBDisplay,-1,'Plot?')
1630            Indx[plotRB.GetId()] = rbId
1631            plotRB.Bind(wx.EVT_CHECKBOX,OnPlotRB)
1632            nameSizer.Add(plotRB,0,wx.ALIGN_CENTER_VERTICAL)
1633            nameSizer.Add((5,0),)
1634            if not rbData['useCount']:
1635                delRB = wx.CheckBox(ResidueRBDisplay,-1,'Delete?')
1636                Indx[delRB.GetId()] = rbId
1637                delRB.Bind(wx.EVT_CHECKBOX,OnDelRB)
1638                nameSizer.Add(delRB,0,wx.ALIGN_CENTER_VERTICAL)
1639            return nameSizer
1640           
1641        def rbResidues(rbId,rbData):
1642           
1643            def TypeSelect(event):
1644                AtInfo = data['Residue']['AtInfo']
1645                r,c = event.GetRow(),event.GetCol()
1646                if vecGrid.GetColLabelValue(c) == 'Type':
1647                    PE = G2elemGUI.PickElement(G2frame,oneOnly=True)
1648                    if PE.ShowModal() == wx.ID_OK:
1649                        if PE.Elem != 'None':
1650                            El = PE.Elem.strip().lower().capitalize()
1651                            if El not in AtInfo:
1652                                Info = G2elem.GetAtomInfo(El)
1653                                AtInfo[El] = [Info['Drad']['Color']]
1654                            rbData['rbTypes'][r] = El
1655                            vecGrid.SetCellValue(r,c,El)
1656                    PE.Destroy()
1657
1658            def ChangeCell(event):
1659                r,c =  event.GetRow(),event.GetCol()
1660                if r >= 0 and (0 <= c < 3):
1661                    try:
1662                        val = float(vecGrid.GetCellValue(r,c))
1663                        rbData['rbVect'][imag][r][c] = val
1664                    except ValueError:
1665                        pass
1666                       
1667            def RowSelect(event):
1668                r,c =  event.GetRow(),event.GetCol()
1669                if c < 0:                   #only row clicks
1670                    for vecgrid in resList:
1671                        vecgrid.ClearSelection()
1672                    vecGrid.SelectRow(r,True)
1673
1674            def OnRefSel(event):
1675                Obj = event.GetEventObject()
1676                iref,res,jref = Indx[Obj.GetId()]
1677                sel = Obj.GetValue()
1678                ind = atNames.index(sel)
1679                rbData['rbRef'][iref] = ind
1680                FillRefChoice(rbId,rbData)
1681                for i,ref in enumerate(RefObjs[jref]):
1682                    ref.SetItems([atNames[j] for j in refChoice[rbId][i]])
1683                    ref.SetValue(atNames[rbData['rbRef'][i]])
1684                if not iref:     #origin change
1685                    rbXYZ = rbData['rbXYZ']
1686                    rbXYZ -= rbXYZ[ind]
1687                    res.ClearSelection()
1688                    resTable = res.GetTable()
1689                    for r in range(res.GetNumberRows()):
1690                        row = resTable.GetRowValues(r)
1691                        row[2:4] = rbXYZ[r]
1692                        resTable.SetRowValues(r,row)
1693                    res.ForceRefresh()
1694                    G2plt.PlotRigidBody(G2frame,'Residue',AtInfo,rbData,plotDefaults)
1695               
1696            Types = 2*[wg.GRID_VALUE_STRING,]+3*[wg.GRID_VALUE_FLOAT+':10,5',]
1697            colLabels = ['Name','Type','Cart x','Cart y','Cart z']
1698            table = []
1699            rowLabels = []
1700            for ivec,xyz in enumerate(rbData['rbXYZ']):
1701                table.append([rbData['atNames'][ivec],]+[rbData['rbTypes'][ivec],]+list(xyz))
1702                rowLabels.append(str(ivec))
1703            vecTable = G2G.Table(table,rowLabels=rowLabels,colLabels=colLabels,types=Types)
1704            vecGrid = G2G.GSGrid(ResidueRBDisplay)
1705            Indx[vecGrid.GetId()] = rbId
1706            resList.append(vecGrid)
1707            vecGrid.SetTable(vecTable, True)
1708            vecGrid.Bind(wg.EVT_GRID_CELL_CHANGE, ChangeCell)
1709            vecGrid.Bind(wg.EVT_GRID_CELL_LEFT_DCLICK, TypeSelect)
1710            vecGrid.Bind(wg.EVT_GRID_LABEL_LEFT_CLICK, RowSelect)
1711            attr = wx.grid.GridCellAttr()
1712            attr.SetEditor(G2G.GridFractionEditor(vecGrid))
1713            for c in range(3):
1714                vecGrid.SetColAttr(c, attr)
1715            for row in range(vecTable.GetNumberRows()):
1716                for col in range(5):
1717                    vecGrid.SetCellStyle(row,col,VERY_LIGHT_GREY,True)
1718            vecGrid.SetMargins(0,0)
1719            vecGrid.AutoSizeColumns(False)
1720            vecSizer = wx.BoxSizer()
1721            vecSizer.Add(vecGrid)
1722           
1723            refAtmSizer = wx.BoxSizer(wx.HORIZONTAL)
1724            atNames = rbData['atNames']
1725            rbRef = rbData['rbRef']
1726            if rbData['rbRef'][3] or rbData['useCount']:
1727                refAtmSizer.Add(wx.StaticText(ResidueRBDisplay,-1,
1728                    'Orientation reference atoms A-B-C: %s, %s, %s'%(atNames[rbRef[0]], \
1729                     atNames[rbRef[1]],atNames[rbRef[2]])),0)
1730            else:
1731                refAtmSizer.Add(wx.StaticText(ResidueRBDisplay,-1,
1732                    'Orientation reference atoms A-B-C: '),0,wx.ALIGN_CENTER_VERTICAL)
1733                refObj = [0,0,0]
1734                for i in range(3):
1735                    choices = [atNames[j] for j in refChoice[rbId][i]]
1736                    refSel = wx.ComboBox(ResidueRBDisplay,-1,value='',
1737                        choices=choices,style=wx.CB_READONLY|wx.CB_DROPDOWN)
1738                    refSel.SetValue(atNames[rbRef[i]])
1739                    refSel.Bind(wx.EVT_COMBOBOX, OnRefSel)
1740                    Indx[refSel.GetId()] = [i,vecGrid,len(RefObjs)]
1741                    refObj[i] = refSel
1742                    refAtmSizer.Add(refSel,0,wx.ALIGN_CENTER_VERTICAL)
1743                RefObjs.append(refObj)
1744           
1745            mainSizer = wx.BoxSizer(wx.VERTICAL)
1746            mainSizer.Add(refAtmSizer)
1747            mainSizer.Add(vecSizer)
1748            return mainSizer
1749           
1750        def SeqSizer(angSlide,rbId,iSeq,Seq,atNames):
1751           
1752            def ChangeAngle(event):
1753                Obj = event.GetEventObject()
1754                rbId,Seq = Indx[Obj.GetId()][:2]
1755                val = Seq[2]
1756                try:
1757                    val = float(Obj.GetValue())
1758                    Seq[2] = val
1759                except ValueError:
1760                    pass
1761                Obj.SetValue('%8.2f'%(val))
1762                G2plt.PlotRigidBody(G2frame,'Residue',AtInfo,data['Residue'][rbId],plotDefaults)
1763               
1764            def OnRadBtn(event):
1765                Obj = event.GetEventObject()
1766                Seq,iSeq,angId = Indx[Obj.GetId()]
1767                data['Residue'][rbId]['SelSeq'] = [iSeq,angId]
1768                angSlide.SetValue(int(100*Seq[2]))
1769               
1770            def OnDelBtn(event):
1771                Obj = event.GetEventObject()
1772                rbId,Seq = Indx[Obj.GetId()]
1773                data['Residue'][rbId]['rbSeq'].remove(Seq)       
1774                wx.CallAfter(UpdateResidueRB)
1775           
1776            seqSizer = wx.FlexGridSizer(0,5,2,2)
1777            seqSizer.AddGrowableCol(3,0)
1778            iBeg,iFin,angle,iMove = Seq
1779            ang = wx.TextCtrl(ResidueRBDisplay,-1,'%8.2f'%(angle),size=(50,20))
1780            if not iSeq:
1781                radBt = wx.RadioButton(ResidueRBDisplay,-1,'',style=wx.RB_GROUP)
1782                data['Residue'][rbId]['SelSeq'] = [iSeq,ang.GetId()]
1783            else:
1784                radBt = wx.RadioButton(ResidueRBDisplay,-1,'')
1785            radBt.Bind(wx.EVT_RADIOBUTTON,OnRadBtn)                   
1786            seqSizer.Add(radBt)
1787            delBt = wx.RadioButton(ResidueRBDisplay,-1,'')
1788            delBt.Bind(wx.EVT_RADIOBUTTON,OnDelBtn)
1789            seqSizer.Add(delBt)
1790            bond = wx.TextCtrl(ResidueRBDisplay,-1,'%s %s'%(atNames[iBeg],atNames[iFin]),size=(50,20))
1791            seqSizer.Add(bond,0,wx.ALIGN_CENTER_VERTICAL)
1792            Indx[radBt.GetId()] = [Seq,iSeq,ang.GetId()]
1793            Indx[delBt.GetId()] = [rbId,Seq]
1794            Indx[ang.GetId()] = [rbId,Seq,ang]
1795            ang.Bind(wx.EVT_TEXT_ENTER,ChangeAngle)
1796            ang.Bind(wx.EVT_KILL_FOCUS,ChangeAngle)
1797            seqSizer.Add(ang,0,wx.ALIGN_CENTER_VERTICAL)
1798            atms = ''
1799            for i in iMove:   
1800                atms += ' %s,'%(atNames[i])
1801            moves = wx.TextCtrl(ResidueRBDisplay,-1,atms[:-1],size=(200,20))
1802            seqSizer.Add(moves,1,wx.ALIGN_CENTER_VERTICAL|wx.EXPAND|wx.RIGHT)
1803            return seqSizer
1804           
1805        def SlideSizer():
1806           
1807            def OnSlider(event):
1808                Obj = event.GetEventObject()
1809                rbData = Indx[Obj.GetId()]
1810                iSeq,angId = rbData['SelSeq']
1811                val = float(Obj.GetValue())/100.
1812                rbData['rbSeq'][iSeq][2] = val
1813                Indx[angId][2].SetValue('%8.2f'%(val))
1814                G2plt.PlotRigidBody(G2frame,'Residue',AtInfo,rbData,plotDefaults)
1815           
1816            slideSizer = wx.BoxSizer(wx.HORIZONTAL)
1817            slideSizer.Add(wx.StaticText(ResidueRBDisplay,-1,'Selected torsion angle:'),0)
1818            iSeq,angId = rbData['SelSeq']
1819            angSlide = wx.Slider(ResidueRBDisplay,-1,
1820                int(100*rbData['rbSeq'][iSeq][2]),0,36000,size=(200,20),
1821                style=wx.SL_HORIZONTAL)
1822            angSlide.Bind(wx.EVT_SLIDER, OnSlider)
1823            Indx[angSlide.GetId()] = rbData
1824            slideSizer.Add(angSlide,0)           
1825            return slideSizer,angSlide
1826           
1827        def FillRefChoice(rbId,rbData):
1828            choiceIds = [i for i in range(len(rbData['atNames']))]
1829            for seq in rbData['rbSeq']:
1830                for i in seq[3]:
1831                    try:
1832                        choiceIds.remove(i)
1833                    except ValueError:
1834                        pass
1835            rbRef = rbData['rbRef']
1836            for i in range(3):
1837                try:
1838                    choiceIds.remove(rbRef[i])
1839                except ValueError:
1840                    pass
1841            refChoice[rbId] = [choiceIds[:],choiceIds[:],choiceIds[:]]
1842            for i in range(3):
1843                refChoice[rbId][i].append(rbRef[i])
1844                refChoice[rbId][i].sort()     
1845           
1846        #ResidueRB.DestroyChildren() # bad, deletes scrollbars on Mac!
1847        if ResidueRB.GetSizer():
1848            ResidueRB.GetSizer().Clear(True)
1849        ResidueRBDisplay = wx.Panel(ResidueRB)
1850        ResidueRBSizer = wx.BoxSizer(wx.VERTICAL)
1851        for rbId in data['RBIds']['Residue']:
1852            rbData = data['Residue'][rbId]
1853            FillRefChoice(rbId,rbData)
1854            ResidueRBSizer.Add(rbNameSizer(rbId,rbData),0)
1855            ResidueRBSizer.Add(rbResidues(rbId,rbData),0)
1856            ResidueRBSizer.Add((5,5),0)
1857            if rbData['rbSeq']:
1858                slideSizer,angSlide = SlideSizer()
1859            if len(rbData['rbSeq']):
1860                ResidueRBSizer.Add(wx.StaticText(ResidueRBDisplay,-1,
1861                    'Sel  Del  Bond             Angle      Riding atoms'),
1862                    0,wx.ALIGN_CENTER_VERTICAL)                       
1863            for iSeq,Seq in enumerate(rbData['rbSeq']):
1864                ResidueRBSizer.Add(SeqSizer(angSlide,rbId,iSeq,Seq,rbData['atNames']))
1865            if rbData['rbSeq']:
1866                ResidueRBSizer.Add(slideSizer,)
1867            ResidueRBSizer.Add(wx.StaticText(ResidueRBDisplay,-1,100*'-'))
1868
1869        ResidueRBSizer.Add((5,25),)
1870        ResidueRBSizer.Layout()   
1871        ResidueRBDisplay.SetSizer(ResidueRBSizer,True)
1872        Size = ResidueRBSizer.GetMinSize()
1873        Size[0] += 40
1874        Size[1] = max(Size[1],450) + 20
1875        ResidueRBDisplay.SetSize(Size)
1876        ResidueRB.SetScrollbars(10,10,Size[0]/10-4,Size[1]/10-1)
1877        Size[1] = min(600,Size[1])
1878        G2frame.dataFrame.setSizePosLeft(Size)
1879       
1880    def SetStatusLine(text):
1881        Status.SetStatusText(text)                                     
1882
1883    if G2frame.dataDisplay:
1884        G2frame.dataDisplay.Destroy()
1885    G2gd.SetDataMenuBar(G2frame,G2frame.dataFrame.RigidBodyMenu)
1886    G2frame.dataFrame.SetLabel('Rigid bodies')
1887    if not G2frame.dataFrame.GetStatusBar():
1888        Status = G2frame.dataFrame.CreateStatusBar()
1889    SetStatusLine('')
1890
1891    G2frame.dataDisplay = G2G.GSNoteBook(parent=G2frame.dataFrame,size=G2frame.dataFrame.GetClientSize())
1892
1893    VectorRB = wx.ScrolledWindow(G2frame.dataDisplay)
1894    G2frame.dataDisplay.AddPage(VectorRB,'Vector rigid bodies')
1895    ResidueRB = wx.ScrolledWindow(G2frame.dataDisplay)
1896    G2frame.dataDisplay.AddPage(ResidueRB,'Residue rigid bodies')
1897    UpdateVectorRB()
1898    G2frame.dataDisplay.Bind(wx.aui.EVT_AUINOTEBOOK_PAGE_CHANGED, OnPageChanged)
1899    OnPageChanged(None)
Note: See TracBrowser for help on using the repository browser.