source: trunk/GSASIIconstrGUI.py @ 2481

Last change on this file since 2481 was 2481, checked in by vondreele, 5 years ago

force Transform to delete nonmagnetic atoms if phase made magnetic & add 'mag' to new phase name
fix TOF cosine background function
magnetic structure refinement works (in numeric mode only)
magnetic structure factor calculation correct

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