source: trunk/GSASIIconstrGUI.py @ 3355

Last change on this file since 3355 was 3355, checked in by toby, 4 years ago

name main & plot window against project name, remove other SetTitle? calls

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