source: trunk/GSASIIconstrGUI.py @ 2085

Last change on this file since 2085 was 2085, checked in by vondreele, 7 years ago

allow constraints between Tmin & Tmax (ZigZag/Block? parms)

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