source: trunk/GSASIIconstrGUI.py @ 3371

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

constraint fixes: implement wild-card equivalences, fix use of all for phase selection in atom vars; prevent use of Edit Constr menu items for sym-generated equivalences

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Author Revision URL Id
File size: 94.1 KB
Line 
1# -*- coding: utf-8 -*-
2#GSASIIconstrGUI - constraint GUI routines
3########### SVN repository information ###################
4# $Date: 2018-05-04 22:57:13 +0000 (Fri, 04 May 2018) $
5# $Author: toby $
6# $Revision: 3371 $
7# $URL: trunk/GSASIIconstrGUI.py $
8# $Id: GSASIIconstrGUI.py 3371 2018-05-04 22:57:13Z 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: 3371 $")
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(ph)[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(ph)[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(ph)[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        if vartype is None: return
609        varList = G2obj.SortVariables(varList)
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        enableEditCons = True
1084        if text == 'Histogram/Phase':
1085            G2frame.Page = [page,'hap']
1086            UpdateConstraintPanel(HAPConstr,'HAP')
1087        elif text == 'Histogram':
1088            G2frame.Page = [page,'hst']
1089            UpdateConstraintPanel(HistConstr,'Hist')
1090        elif text == 'Phase':
1091            G2frame.Page = [page,'phs']
1092            G2frame.dataWindow.ConstraintEdit.Enable(G2G.wxID_EQUIVALANCEATOMS,True)
1093#            G2frame.dataWindow.ConstraintEdit.Enable(G2G.wxID_ADDRIDING,True)
1094            if 'DELETED' in str(PhaseConstr):   #seems to be no other way to do this (wx bug)
1095                if GSASIIpath.GetConfigValue('debug'):
1096                    print ('wx error: PhaseConstr not cleanly deleted after Refine')
1097                return
1098            UpdateConstraintPanel(PhaseConstr,'Phase')
1099        elif text == 'Global':
1100            G2frame.Page = [page,'glb']
1101            UpdateConstraintPanel(GlobalConstr,'Global')
1102        else:
1103            enableEditCons = False
1104            G2frame.Page = [page,'sym']
1105            UpdateConstraintPanel(SymConstr,'Sym-Generated')
1106        # remove menu items when not allowed
1107        for i in G2frame.dataWindow.ConstraintEdit.GetMenuItems(): 
1108            i.Enable(enableEditCons)
1109        G2frame.dataWindow.SetDataSize()
1110
1111    def RaisePage(event):
1112        'Respond to a "select tab" menu button'
1113        try:
1114            i = (G2G.wxID_CONSPHASE,
1115                 G2G.wxID_CONSHAP,
1116                 G2G.wxID_CONSHIST,
1117                 G2G.wxID_CONSGLOBAL,
1118                 G2G.wxID_CONSSYM,
1119                ).index(event.GetId())
1120            G2frame.constr.SetSelection(i)
1121            wx.CallAfter(OnPageChanged,None)
1122        except ValueError:
1123            print('Unexpected event in RaisePage')
1124
1125    def SetStatusLine(text):
1126        G2frame.GetStatusBar().SetStatusText(text,1)                                     
1127       
1128    G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.ConstraintMenu)
1129    SetStatusLine('')
1130   
1131    G2frame.Bind(wx.EVT_MENU, OnAddConstraint, id=G2G.wxID_CONSTRAINTADD)
1132    G2frame.Bind(wx.EVT_MENU, OnAddFunction, id=G2G.wxID_FUNCTADD)
1133    G2frame.Bind(wx.EVT_MENU, OnAddEquivalence, id=G2G.wxID_EQUIVADD)
1134    G2frame.Bind(wx.EVT_MENU, OnAddHold, id=G2G.wxID_HOLDADD)
1135    G2frame.Bind(wx.EVT_MENU, OnAddAtomEquiv, id=G2G.wxID_EQUIVALANCEATOMS)
1136#    G2frame.Bind(wx.EVT_MENU, OnAddRiding, id=G2G.wxID_ADDRIDING)
1137    # tab commands
1138    for id in (G2G.wxID_CONSPHASE,
1139               G2G.wxID_CONSHAP,
1140               G2G.wxID_CONSHIST,
1141               G2G.wxID_CONSGLOBAL,
1142               G2G.wxID_CONSSYM,
1143               ):
1144        G2frame.Bind(wx.EVT_MENU, RaisePage,id=id)
1145
1146    #G2frame.constr = G2G.GSNoteBook(parent=G2frame.dataWindow,size=G2frame.dataWindow.GetClientSize())
1147    G2frame.constr = G2G.GSNoteBook(parent=G2frame.dataWindow)
1148    G2frame.dataWindow.GetSizer().Add(G2frame.constr,1,wx.ALL|wx.EXPAND)
1149    # note that order of pages is hard-coded in RaisePage
1150    PhaseConstr = wx.ScrolledWindow(G2frame.constr)
1151    G2frame.constr.AddPage(PhaseConstr,'Phase')
1152    HAPConstr = wx.ScrolledWindow(G2frame.constr)
1153    G2frame.constr.AddPage(HAPConstr,'Histogram/Phase')
1154    HistConstr = wx.ScrolledWindow(G2frame.constr)
1155    G2frame.constr.AddPage(HistConstr,'Histogram')
1156    GlobalConstr = wx.ScrolledWindow(G2frame.constr)
1157    G2frame.constr.AddPage(GlobalConstr,'Global')
1158    SymConstr = wx.ScrolledWindow(G2frame.constr)
1159    G2frame.constr.AddPage(SymConstr,'Sym-Generated')
1160    wx.CallAfter(OnPageChanged,None)
1161    G2frame.constr.Bind(wx.aui.EVT_AUINOTEBOOK_PAGE_CHANGED, OnPageChanged)
1162    # validate all the constrants -- should not see any errors here normally
1163    allcons = []
1164    for key in data:
1165        if key.startswith('_'): continue
1166        allcons += data[key]
1167    if not len(allcons): return
1168    errmsg,warnmsg = CheckConstraints(allcons)
1169    if errmsg:
1170        G2frame.ErrorDialog('Constraint Error',
1171                            'Error in constraints:\n'+errmsg+'\nCheck console output for more information',
1172                            parent=G2frame)
1173        print (errmsg)
1174        print (G2mv.VarRemapShow([],True))
1175    elif warnmsg:
1176        print ('Unexpected contraint warning:\n'+warnmsg)
1177
1178################################################################################
1179#### Make nuclear-magnetic phase constraints - called by OnTransform in G2phsGUI
1180################################################################################       
1181       
1182def MagConstraints(G2frame,oldPhase,newPhase,Trans,Vec,atCodes):
1183    '''Add constraints for new magnetic phase created via transformation of old
1184    nuclear one
1185    NB: A = [G11,G22,G33,2*G12,2*G13,2*G23]
1186    '''
1187   
1188    def SetUniqAj(pId,iA,Aname,SGLaue):
1189        if SGLaue in ['4/m','4/mmm'] and iA in [0,1]:
1190            parm = '%d::%s'%(pId,'A0')
1191        elif SGLaue in ['m3','m3m'] and iA in [0,1,2]:
1192            parm = '%d::%s'%(pId,'A0')
1193        elif SGLaue in ['6/m','6/mmm','3m1', '31m', '3'] and iA in [0,1,3]:
1194            parm = '%d::%s'%(pId,'A0')
1195        elif SGLaue in ['3R', '3mR']:
1196            if ia in [0,1,2]:
1197                parm = '%d::%s'%(pId,'A0')
1198            else:
1199                parm = '%d::%s'%(pId,'A3')
1200        else:
1201            parm = '%d::%s'%(pId,Aname)
1202        return parm
1203       
1204    Histograms,Phases = G2frame.GetUsedHistogramsAndPhasesfromTree()
1205    UseList = newPhase['Histograms']
1206    detTrans = np.abs(nl.det(Trans))
1207    invTrans = nl.inv(Trans)
1208#    print 'invTrans',invTrans
1209    nAcof = G2lat.cell2A(newPhase['General']['Cell'][1:7])
1210   
1211    opId = oldPhase['pId']
1212    npId = newPhase['pId']
1213    cx,ct,cs,cia = newPhase['General']['AtomPtrs']
1214    nAtoms = newPhase['Atoms']
1215    oSGData = oldPhase['General']['SGData']
1216    nSGData = newPhase['General']['SGData']
1217    oAcof = G2lat.cell2A(oldPhase['General']['Cell'][1:7])
1218    nAcof = G2lat.cell2A(newPhase['General']['Cell'][1:7])
1219    item = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'Constraints')
1220    if not item:
1221        return
1222    constraints = G2frame.GPXtree.GetItemPyData(item)
1223    parmDict = {}
1224    varyList = []
1225    for ia,code in enumerate(atCodes):
1226        atom = nAtoms[ia]
1227        siteSym = G2spc.SytSym(atom[cx:cx+3],nSGData)[0]
1228        CSX = G2spc.GetCSxinel(siteSym)
1229        item = code.split('+')[0]
1230        iat,opr = item.split(':')
1231        Nop = abs(int(opr))%100-1
1232        if '-' in opr:
1233            Nop *= -1
1234        Opr = oldPhase['General']['SGData']['SGOps'][abs(Nop)][0]
1235        if Nop < 0:         #inversion
1236            Opr *= -1
1237        XOpr = np.inner(Opr,Trans.T)
1238        names = ['dAx','dAy','dAz']
1239        for ix,name in enumerate(names):
1240            IndpCon = [1.0,G2obj.G2VarObj('%d::%s:%s'%(opId,name,iat))]
1241            DepCons = []
1242            for iop,opval in enumerate(XOpr[ix]):
1243                if opval and CSX[0][ix]:    #-opval from defn of dAx, etc.
1244                    DepCons.append([-opval,G2obj.G2VarObj('%d::%s:%d'%(npId,names[iop],ia))])
1245            if len(DepCons) == 1:
1246                constraints['Phase'].append([IndpCon,DepCons[0],None,None,'e'])
1247            elif len(DepCons) > 1:
1248                for Dep in DepCons:
1249                    Dep[0] *= -1
1250                constraints['Phase'].append([IndpCon]+DepCons+[0.0,None,'c'])
1251        for name in ['Afrac','AUiso']:
1252            IndpCon = [1.0,G2obj.G2VarObj('%d::%s:%s'%(opId,name,iat))]
1253            DepCons = [1.0,G2obj.G2VarObj('%d::%s:%d'%(npId,name,ia))]
1254            constraints['Phase'].append([IndpCon,DepCons,None,None,'e'])
1255        #how do I do Uij's for most Trans?
1256    Anames = [['A0','A3','A4'],['A3','A1','A5'],['A4','A5','A2']]
1257    As = ['A0','A1','A2','A3','A4','A5']
1258    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]]
1259    Axes = ['a','b','c']
1260    Holds = []
1261    for iA,Aid in enumerate(Aids):
1262        parm = SetUniqAj(opId,iA,Aid[2],oSGData['SGLaue'])
1263        parmDict[parm] = oAcof[iA]
1264        varyList.append(parm)
1265        IndpCon = [1.0,G2obj.G2VarObj(parm)]
1266        DepCons = []
1267        for iat in range(3):
1268            if nSGData['SGLaue'] in ['-1','2/m']:       #set holds
1269                if (abs(nAcof[iA]) < 1.e-8) and (abs(Trans[Aid[0],Aid[1]]) < 1.e-8):
1270                    if Axes[iat] != oSGData['SGUniq'] and oSGData['SGLaue'] != nSGData['SGLaue']:
1271                        HoldObj = G2obj.G2VarObj('%d::%s'%(npId,Aid[2]))
1272                        if not HoldObj in Holds: 
1273                            constraints['Phase'].append([[0.0,HoldObj],None,None,'h'])
1274                            Holds.append(HoldObj)
1275                            continue
1276#            print iA,Aid,iat,invTrans[iat][Aid[0]],invTrans[Aid[1]][iat],Anames[Aid[0]][Aid[1]],parm
1277            if abs(invTrans[iat,Aid[1]]) > 1.e-8 and abs(nAcof[iA]) > 1.e-8:
1278                parm = SetUniqAj(npId,iA,Anames[Aid[0]][Aid[1]],nSGData['SGLaue'])
1279                parmDict[parm] = nAcof[As.index(Aid[2])]
1280                if not parm in varyList:
1281                    varyList.append(parm)
1282                DepCons.append([Trans[Aid[0],Aid[0]]*Trans[Aid[1],Aid[1]],G2obj.G2VarObj(parm)])
1283        if len(DepCons) == 1:
1284            constraints['Phase'].append([IndpCon,DepCons[0],None,None,'e'])
1285        elif len(DepCons) > 1:       
1286            for Dep in DepCons:
1287                Dep[0] *= -1
1288            constraints['Phase'].append([IndpCon]+DepCons+[0.0,None,'c'])
1289#    constDict,fixedList,ignored = G2stIO.ProcessConstraints(constraints['Phase'])
1290#    groups,parmlist = G2mv.GroupConstraints(constDict)
1291#    G2mv.GenerateConstraints(groups,parmlist,varyList,constDict,fixedList,parmDict)
1292#    print 'old',parmDict
1293#    G2mv.Dict2Map(parmDict,varyList)
1294#    print 'new',parmDict
1295    for hId,hist in enumerate(UseList):    #HAP - seems OK
1296        ohapkey = '%d:%d:'%(opId,hId)
1297        nhapkey = '%d:%d:'%(npId,hId)
1298        IndpCon = [1.0,G2obj.G2VarObj(ohapkey+'Scale')]
1299        DepCons = [detTrans,G2obj.G2VarObj(nhapkey+'Scale')]
1300        constraints['HAP'].append([IndpCon,DepCons,None,None,'e'])
1301        for name in ['Size;i','Mustrain;i']:
1302            IndpCon = [1.0,G2obj.G2VarObj(ohapkey+name)]
1303            DepCons = [1.0,G2obj.G2VarObj(nhapkey+name)]
1304            constraints['HAP'].append([IndpCon,DepCons,None,None,'e'])
1305       
1306################################################################################
1307#### Rigid bodies
1308################################################################################
1309
1310def UpdateRigidBodies(G2frame,data):
1311    '''Called when Rigid bodies tree item is selected.
1312    Displays the rigid bodies in the data window
1313    '''
1314    if not data.get('RBIds') or not data:
1315        data.update({'Vector':{'AtInfo':{}},'Residue':{'AtInfo':{}},
1316            'RBIds':{'Vector':[],'Residue':[]}})       #empty/bad dict - fill it
1317           
1318    global resList,rbId
1319    Indx = {}
1320    resList = []
1321    plotDefaults = {'oldxy':[0.,0.],'Quaternion':[0.,0.,0.,1.],'cameraPos':30.,'viewDir':[0,0,1],}
1322
1323    G2frame.rbBook = G2G.GSNoteBook(parent=G2frame.dataWindow)
1324    G2frame.dataWindow.GetSizer().Add(G2frame.rbBook,1,wx.ALL|wx.EXPAND)
1325    VectorRB = wx.ScrolledWindow(G2frame.rbBook)
1326    VectorRBDisplay = wx.Panel(VectorRB)
1327    G2frame.rbBook.AddPage(VectorRB,'Vector rigid bodies')
1328    ResidueRB = wx.ScrolledWindow(G2frame.rbBook)
1329    ResidueRBDisplay = wx.Panel(ResidueRB)
1330    G2frame.rbBook.AddPage(ResidueRB,'Residue rigid bodies')
1331   
1332    def OnPageChanged(event):
1333        global resList
1334        resList = []
1335        if event:       #page change event!
1336            page = event.GetSelection()
1337        else:
1338            page = G2frame.rbBook.GetSelection()
1339        #G2frame.rbBook.SetSize(G2frame.dataWindow.GetClientSize())    #TODO -almost right
1340        G2frame.rbBook.ChangeSelection(page)
1341        text = G2frame.rbBook.GetPageText(page)
1342        if text == 'Vector rigid bodies':
1343            G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.VectorBodyMenu)
1344            G2frame.Bind(wx.EVT_MENU, AddVectorRB, id=G2G.wxID_VECTORBODYADD)
1345            G2frame.Page = [page,'vrb']
1346            UpdateVectorRB()
1347        elif text == 'Residue rigid bodies':
1348            G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.RigidBodyMenu)
1349            G2frame.Bind(wx.EVT_MENU, AddResidueRB, id=G2G.wxID_RIGIDBODYADD)
1350            G2frame.Bind(wx.EVT_MENU, OnImportRigidBody, id=G2G.wxID_RIGIDBODYIMPORT)
1351            G2frame.Bind(wx.EVT_MENU, OnDefineTorsSeq, id=G2G.wxID_RESIDUETORSSEQ) #enable only if residue RBs exist?
1352            G2frame.Page = [page,'rrb']
1353            UpdateResidueRB()
1354           
1355    def getMacroFile(macName):
1356        defDir = os.path.join(os.path.split(__file__)[0],'GSASIImacros')
1357        dlg = wx.FileDialog(G2frame,message='Choose '+macName+' rigid body macro file',
1358            defaultDir=defDir,defaultFile="",wildcard="GSAS-II macro file (*.mac)|*.mac",
1359            style=wx.FD_OPEN | wx.CHANGE_DIR)
1360        try:
1361            if dlg.ShowModal() == wx.ID_OK:
1362                macfile = dlg.GetPath()
1363                macro = open(macfile,'Ur')
1364                head = macro.readline()
1365                if macName not in head:
1366                    print (head)
1367                    print ('**** ERROR - wrong restraint macro file selected, try again ****')
1368                    macro = []
1369            else: # cancel was pressed
1370                macro = []
1371        finally:
1372            dlg.Destroy()
1373        return macro        #advanced past 1st line
1374       
1375    def getTextFile():
1376        dlg = wx.FileDialog(G2frame,'Choose rigid body text file', '.', '',
1377            "GSAS-II text file (*.txt)|*.txt|XYZ file (*.xyz)|*.xyz|"
1378            "Sybyl mol2 file (*.mol2)|*.mol2|PDB file (*.pdb;*.ent)|*.pdb;*.ent",
1379            wx.FD_OPEN | wx.CHANGE_DIR)
1380        try:
1381            if dlg.ShowModal() == wx.ID_OK:
1382                txtfile = dlg.GetPath()
1383                ext = os.path.splitext(txtfile)[1]
1384                text = open(txtfile,'Ur')
1385            else: # cancel was pressed
1386                ext = ''
1387                text = []
1388        finally:
1389            dlg.Destroy()
1390        if 'ent' in ext:
1391            ext = '.pdb'
1392        return text,ext.lower()
1393       
1394    def OnImportRigidBody(event):
1395        page = G2frame.rbBook.GetSelection()
1396        if 'Vector' in G2frame.rbBook.GetPageText(page):
1397            pass
1398        elif 'Residue' in G2frame.rbBook.GetPageText(page):
1399            ImportResidueRB()
1400           
1401    def AddVectorRB(event):
1402        AtInfo = data['Vector']['AtInfo']
1403        dlg = G2G.MultiIntegerDialog(G2frame,'New Rigid Body',['No. atoms','No. translations'],[1,1])
1404        if dlg.ShowModal() == wx.ID_OK:
1405            nAtoms,nTrans = dlg.GetValues()
1406            rbId = ran.randint(0,sys.maxsize)
1407            vecMag = [1.0 for i in range(nTrans)]
1408            vecRef = [False for i in range(nTrans)]
1409            vecVal = [np.zeros((nAtoms,3)) for j in range(nTrans)]
1410            rbTypes = ['C' for i in range(nAtoms)]
1411            Info = G2elem.GetAtomInfo('C')
1412            AtInfo['C'] = [Info['Drad'],Info['Color']]
1413            data['Vector'][rbId] = {'RBname':'UNKRB','VectMag':vecMag,'rbXYZ':np.zeros((nAtoms,3)),
1414                'rbRef':[0,1,2,False],'VectRef':vecRef,'rbTypes':rbTypes,'rbVect':vecVal,'useCount':0}
1415            data['RBIds']['Vector'].append(rbId)
1416        dlg.Destroy()
1417        UpdateVectorRB()
1418       
1419    def AddResidueRB(event):
1420        AtInfo = data['Residue']['AtInfo']
1421        macro = getMacroFile('rigid body')
1422        if not macro:
1423            return
1424        macStr = macro.readline()
1425        while macStr:
1426            items = macStr.split()
1427            if 'I' == items[0]:
1428                rbId = ran.randint(0,sys.maxsize)
1429                rbName = items[1]
1430                rbTypes = []
1431                rbXYZ = []
1432                rbSeq = []
1433                atNames = []
1434                nAtms,nSeq,nOrig,mRef,nRef = [int(items[i]) for i in [2,3,4,5,6]]
1435                for iAtm in range(nAtms):
1436                    macStr = macro.readline().split()
1437                    atName = macStr[0]
1438                    atType = macStr[1]
1439                    atNames.append(atName)
1440                    rbXYZ.append([float(macStr[i]) for i in [2,3,4]])
1441                    rbTypes.append(atType)
1442                    if atType not in AtInfo:
1443                        Info = G2elem.GetAtomInfo(atType)
1444                        AtInfo[atType] = [Info['Drad'],Info['Color']]
1445                rbXYZ = np.array(rbXYZ)-np.array(rbXYZ[nOrig-1])
1446                for iSeq in range(nSeq):
1447                    macStr = macro.readline().split()
1448                    mSeq = int(macStr[0])
1449                    for jSeq in range(mSeq):
1450                        macStr = macro.readline().split()
1451                        iBeg = int(macStr[0])-1
1452                        iFin = int(macStr[1])-1
1453                        angle = 0.0
1454                        nMove = int(macStr[2])
1455                        iMove = [int(macStr[i])-1 for i in range(3,nMove+3)]
1456                        rbSeq.append([iBeg,iFin,angle,iMove])
1457                data['Residue'][rbId] = {'RBname':rbName,'rbXYZ':rbXYZ,'rbTypes':rbTypes,
1458                    'atNames':atNames,'rbRef':[nOrig-1,mRef-1,nRef-1,True],'rbSeq':rbSeq,
1459                    'SelSeq':[0,0],'useCount':0}
1460                data['RBIds']['Residue'].append(rbId)
1461                print ('Rigid body '+rbName+' added')
1462            macStr = macro.readline()
1463        macro.close()
1464        UpdateResidueRB()
1465       
1466    def ImportResidueRB():
1467        AtInfo = data['Residue']['AtInfo']
1468        text,ext = getTextFile()
1469        if not text:
1470            return
1471        rbId = ran.randint(0,sys.maxsize)
1472        rbTypes = []
1473        rbXYZ = []
1474        atNames = []
1475        txtStr = text.readline()
1476        if 'xyz' in ext:
1477            txtStr = text.readline()
1478            txtStr = text.readline()
1479        elif 'mol2' in ext:
1480            while 'ATOM' not in txtStr:
1481                txtStr = text.readline()
1482            txtStr = text.readline()
1483        elif 'pdb' in ext:
1484            while 'ATOM' not in txtStr[:6] and 'HETATM' not in txtStr[:6]:
1485                txtStr = text.readline()
1486                #print txtStr
1487        items = txtStr.split()
1488        while len(items):
1489            if 'txt' in ext:
1490                atName = items[0]
1491                atType = items[1]
1492                rbXYZ.append([float(items[i]) for i in [2,3,4]])
1493            elif 'xyz' in ext:
1494                atType = items[0]
1495                rbXYZ.append([float(items[i]) for i in [1,2,3]])
1496                atName = atType+str(len(rbXYZ))
1497            elif 'mol2' in ext:
1498                atType = items[1]
1499                atName = items[1]+items[0]
1500                rbXYZ.append([float(items[i]) for i in [2,3,4]])
1501            elif 'pdb' in ext:
1502                atType = items[-1]
1503                atName = items[2]
1504                xyz = txtStr[30:55].split()                   
1505                rbXYZ.append([float(x) for x in xyz])
1506            atNames.append(atName)
1507            rbTypes.append(atType)
1508            if atType not in AtInfo:
1509                Info = G2elem.GetAtomInfo(atType)
1510                AtInfo[atType] = [Info['Drad'],Info['Color']]
1511            txtStr = text.readline()
1512            if 'mol2' in ext and 'BOND' in txtStr:
1513                break
1514            if 'pdb' in ext and ('ATOM' not in txtStr[:6] and 'HETATM' not in txtStr[:6]):
1515                break
1516            items = txtStr.split()
1517        if len(atNames) < 3:
1518            G2G.G2MessageBox(G2frame,'Not enough atoms in rigid body; must be 3 or more')
1519        else:
1520            rbXYZ = np.array(rbXYZ)-np.array(rbXYZ[0])
1521            Xxyz = rbXYZ[1]
1522            X = Xxyz/np.sqrt(np.sum(Xxyz**2))
1523            Yxyz = rbXYZ[2]
1524            Y = Yxyz/np.sqrt(np.sum(Yxyz**2))
1525            Mat = G2mth.getRBTransMat(X,Y)
1526            rbXYZ = np.inner(Mat,rbXYZ).T
1527            data['Residue'][rbId] = {'RBname':'UNKRB','rbXYZ':rbXYZ,'rbTypes':rbTypes,
1528                'atNames':atNames,'rbRef':[0,1,2,False],'rbSeq':[],'SelSeq':[0,0],'useCount':0}
1529            data['RBIds']['Residue'].append(rbId)
1530            print ('Rigid body UNKRB added')
1531        text.close()
1532        UpdateResidueRB(rbId)
1533       
1534    def FindNeighbors(Orig,XYZ,atTypes,atNames,AtInfo):
1535        Radii = []
1536        for Atype in atTypes:
1537            Radii.append(AtInfo[Atype][0])
1538        Radii = np.array(Radii)
1539        Neigh = []
1540        Dx = XYZ-XYZ[Orig]
1541        dist = np.sqrt(np.sum(Dx**2,axis=1))
1542        sumR = Radii[Orig]+Radii
1543        IndB = ma.nonzero(ma.masked_greater(dist-0.85*sumR,0.))
1544        for j in IndB[0]:
1545            if j != Orig and atTypes[j] != 'H':
1546                Neigh.append(atNames[j])
1547        return Neigh
1548       
1549    def FindAllNeighbors(XYZ,atTypes,atNames,AtInfo):
1550        NeighDict = {}
1551        for iat,xyz in enumerate(atNames):
1552            NeighDict[atNames[iat]] = FindNeighbors(iat,XYZ,atTypes,atNames,AtInfo)
1553        return NeighDict
1554       
1555    def FindRiding(Orig,Pivot,NeighDict):
1556        riding = [Orig,Pivot]
1557        iAdd = 1
1558        new = True
1559        while new:
1560            newAtms = NeighDict[riding[iAdd]]
1561            for At in newAtms:
1562                new = False
1563                if At not in riding:
1564                    riding.append(At)
1565                    new = True
1566            iAdd += 1
1567            if iAdd < len(riding):
1568                new = True
1569        return riding[2:]
1570                       
1571    def OnDefineTorsSeq(event):
1572        global rbId
1573        rbData = data['Residue'][rbId]
1574        if not len(rbData):
1575            return
1576        atNames = rbData['atNames']
1577        AtInfo = data['Residue']['AtInfo']
1578        atTypes = rbData['rbTypes']
1579        XYZ = rbData['rbXYZ']
1580        neighDict = FindAllNeighbors(XYZ,atTypes,atNames,AtInfo)
1581        TargList = []           
1582        dlg = wx.SingleChoiceDialog(G2frame,'Select origin atom for torsion sequence','Origin atom',rbData['atNames'])
1583        if dlg.ShowModal() == wx.ID_OK:
1584            Orig = dlg.GetSelection()
1585            TargList = neighDict[atNames[Orig]]
1586        dlg.Destroy()
1587        if not len(TargList):
1588            return
1589        dlg = wx.SingleChoiceDialog(G2frame,'Select pivot atom for torsion sequence','Pivot atom',TargList)
1590        if dlg.ShowModal() == wx.ID_OK:
1591            Piv = atNames.index(TargList[dlg.GetSelection()])
1592            riding = FindRiding(atNames[Orig],atNames[Piv],neighDict)
1593            Riding = []
1594            for atm in riding:
1595                Riding.append(atNames.index(atm))
1596            rbData['rbSeq'].append([Orig,Piv,0.0,Riding])           
1597        dlg.Destroy()
1598        UpdateResidueRB(rbId)
1599
1600    def UpdateVectorRB(Scroll=0):
1601        AtInfo = data['Vector']['AtInfo']
1602        refChoice = {}
1603        if 'DELETED' in str(G2frame.GetStatusBar()):   #seems to be no other way to do this (wx bug)
1604            if GSASIIpath.GetConfigValue('debug'):
1605                print ('wx error: Rigid Body/Status not cleanly deleted after Refine')
1606            return
1607        SetStatusLine(' You may use e.g. "c60" or "s60" for a vector entry')
1608        def rbNameSizer(rbId,rbData):
1609
1610            def OnRBName(event):
1611                event.Skip()
1612                Obj = event.GetEventObject()
1613                rbData['RBname'] = Obj.GetValue()
1614               
1615            def OnDelRB(event):
1616                Obj = event.GetEventObject()
1617                rbId = Indx[Obj.GetId()]
1618                if rbId in data['Vector']:
1619                    del data['Vector'][rbId]
1620                    data['RBIds']['Vector'].remove(rbId)
1621                    rbData['useCount'] -= 1
1622                wx.CallAfter(UpdateVectorRB)
1623               
1624            def OnPlotRB(event):
1625                Obj = event.GetEventObject()
1626                Obj.SetValue(False)
1627                G2plt.PlotRigidBody(G2frame,'Vector',AtInfo,rbData,plotDefaults)
1628           
1629            nameSizer = wx.BoxSizer(wx.HORIZONTAL)
1630            nameSizer.Add(wx.StaticText(VectorRBDisplay,-1,'Rigid body name: '),
1631                0,wx.ALIGN_CENTER_VERTICAL)
1632            RBname = wx.TextCtrl(VectorRBDisplay,-1,rbData['RBname'])
1633            Indx[RBname.GetId()] = rbId
1634            RBname.Bind(wx.EVT_TEXT_ENTER,OnRBName)
1635            RBname.Bind(wx.EVT_KILL_FOCUS,OnRBName)
1636            nameSizer.Add(RBname,0,wx.ALIGN_CENTER_VERTICAL)
1637            nameSizer.Add((5,0),)
1638            plotRB = wx.CheckBox(VectorRBDisplay,-1,'Plot?')
1639            Indx[plotRB.GetId()] = rbId
1640            plotRB.Bind(wx.EVT_CHECKBOX,OnPlotRB)
1641            nameSizer.Add(plotRB,0,wx.ALIGN_CENTER_VERTICAL)
1642            nameSizer.Add((5,0),)
1643            if not rbData['useCount']:
1644                delRB = wx.CheckBox(VectorRBDisplay,-1,'Delete?')
1645                Indx[delRB.GetId()] = rbId
1646                delRB.Bind(wx.EVT_CHECKBOX,OnDelRB)
1647                nameSizer.Add(delRB,0,wx.ALIGN_CENTER_VERTICAL)
1648            return nameSizer
1649           
1650        def rbRefAtmSizer(rbId,rbData):
1651           
1652            def OnRefSel(event):
1653                Obj = event.GetEventObject()
1654                iref = Indx[Obj.GetId()]
1655                sel = Obj.GetValue()
1656                rbData['rbRef'][iref] = atNames.index(sel)
1657                FillRefChoice(rbId,rbData)
1658           
1659            refAtmSizer = wx.BoxSizer(wx.HORIZONTAL)
1660            atNames = [name+str(i) for i,name in enumerate(rbData['rbTypes'])]
1661            rbRef = rbData.get('rbRef',[0,1,2,False])
1662            rbData['rbRef'] = rbRef
1663            if rbData['useCount']:
1664                refAtmSizer.Add(wx.StaticText(VectorRBDisplay,-1,
1665                    'Orientation reference atoms A-B-C: %s, %s, %s'%(atNames[rbRef[0]], \
1666                     atNames[rbRef[1]],atNames[rbRef[2]])),0)
1667            else:
1668                refAtmSizer.Add(wx.StaticText(VectorRBDisplay,-1,
1669                    'Orientation reference atoms A-B-C: '),0,wx.ALIGN_CENTER_VERTICAL)
1670                for i in range(3):
1671                    choices = [atNames[j] for j in refChoice[rbId][i]]
1672                    refSel = wx.ComboBox(VectorRBDisplay,-1,value='',
1673                        choices=choices,style=wx.CB_READONLY|wx.CB_DROPDOWN)
1674                    refSel.SetValue(atNames[rbRef[i]])
1675                    refSel.Bind(wx.EVT_COMBOBOX, OnRefSel)
1676                    Indx[refSel.GetId()] = i
1677                    refAtmSizer.Add(refSel,0,wx.ALIGN_CENTER_VERTICAL)
1678            return refAtmSizer
1679                       
1680        def rbVectMag(rbId,imag,rbData):
1681           
1682            def OnRBVectorMag(event):
1683                event.Skip()
1684                Obj = event.GetEventObject()
1685                rbId,imag = Indx[Obj.GetId()]
1686                try:
1687                    val = float(Obj.GetValue())
1688                    if val <= 0.:
1689                        raise ValueError
1690                    rbData['VectMag'][imag] = val
1691                except ValueError:
1692                    pass
1693                Obj.SetValue('%8.4f'%(val))
1694                wx.CallAfter(UpdateVectorRB,VectorRB.GetScrollPos(wx.VERTICAL))
1695                G2plt.PlotRigidBody(G2frame,'Vector',AtInfo,data['Vector'][rbId],plotDefaults)
1696               
1697            def OnRBVectorRef(event):
1698                Obj = event.GetEventObject()
1699                rbId,imag = Indx[Obj.GetId()]
1700                rbData['VectRef'][imag] = Obj.GetValue()
1701                       
1702            magSizer = wx.wx.BoxSizer(wx.HORIZONTAL)
1703            magSizer.Add(wx.StaticText(VectorRBDisplay,-1,'Translation magnitude: '),
1704                0,wx.ALIGN_CENTER_VERTICAL)
1705            magValue = wx.TextCtrl(VectorRBDisplay,-1,'%8.4f'%(rbData['VectMag'][imag]))
1706            Indx[magValue.GetId()] = [rbId,imag]
1707            magValue.Bind(wx.EVT_TEXT_ENTER,OnRBVectorMag)
1708            magValue.Bind(wx.EVT_KILL_FOCUS,OnRBVectorMag)
1709            magSizer.Add(magValue,0,wx.ALIGN_CENTER_VERTICAL)
1710            magSizer.Add((5,0),)
1711            magref = wx.CheckBox(VectorRBDisplay,-1,label=' Refine?') 
1712            magref.SetValue(rbData['VectRef'][imag])
1713            magref.Bind(wx.EVT_CHECKBOX,OnRBVectorRef)
1714            Indx[magref.GetId()] = [rbId,imag]
1715            magSizer.Add(magref,0,wx.ALIGN_CENTER_VERTICAL)
1716            return magSizer
1717           
1718        def rbVectors(rbId,imag,mag,XYZ,rbData):
1719
1720            def TypeSelect(event):
1721                AtInfo = data['Vector']['AtInfo']
1722                r,c = event.GetRow(),event.GetCol()
1723                if vecGrid.GetColLabelValue(c) == 'Type':
1724                    PE = G2elemGUI.PickElement(G2frame,oneOnly=True)
1725                    if PE.ShowModal() == wx.ID_OK:
1726                        if PE.Elem != 'None':
1727                            El = PE.Elem.strip().lower().capitalize()
1728                            if El not in AtInfo:
1729                                Info = G2elem.GetAtomInfo(El)
1730                                AtInfo[El] = [Info['Drad'],Info['Color']]
1731                            rbData['rbTypes'][r] = El
1732                            vecGrid.SetCellValue(r,c,El)
1733                    PE.Destroy()
1734                wx.CallAfter(UpdateVectorRB,VectorRB.GetScrollPos(wx.VERTICAL))
1735
1736            def ChangeCell(event):
1737                r,c =  event.GetRow(),event.GetCol()
1738                if r >= 0 and (0 <= c < 3):
1739                    try:
1740                        val = float(vecGrid.GetCellValue(r,c))
1741                        rbData['rbVect'][imag][r][c] = val
1742                    except ValueError:
1743                        pass
1744                G2plt.PlotRigidBody(G2frame,'Vector',AtInfo,data['Vector'][rbId],plotDefaults)
1745                wx.CallAfter(UpdateVectorRB,VectorRB.GetScrollPos(wx.VERTICAL))
1746
1747            vecSizer = wx.BoxSizer()
1748            Types = 3*[wg.GRID_VALUE_FLOAT+':10,5',]+[wg.GRID_VALUE_STRING,]+3*[wg.GRID_VALUE_FLOAT+':10,5',]
1749            colLabels = ['Vector x','Vector y','Vector z','Type','Cart x','Cart y','Cart z']
1750            table = []
1751            rowLabels = []
1752            for ivec,xyz in enumerate(rbData['rbVect'][imag]):
1753                table.append(list(xyz)+[rbData['rbTypes'][ivec],]+list(XYZ[ivec]))
1754                rowLabels.append(str(ivec))
1755            vecTable = G2G.Table(table,rowLabels=rowLabels,colLabels=colLabels,types=Types)
1756            vecGrid = G2G.GSGrid(VectorRBDisplay)
1757            vecGrid.SetTable(vecTable, True)
1758            if 'phoenix' in wx.version():
1759                vecGrid.Bind(wg.EVT_GRID_CELL_CHANGED, ChangeCell)
1760            else:
1761                vecGrid.Bind(wg.EVT_GRID_CELL_CHANGE, ChangeCell)
1762            if not imag:
1763                vecGrid.Bind(wg.EVT_GRID_CELL_LEFT_DCLICK, TypeSelect)
1764            attr = wx.grid.GridCellAttr()
1765            attr.SetEditor(G2G.GridFractionEditor(vecGrid))
1766            for c in range(3):
1767                vecGrid.SetColAttr(c, attr)
1768            for row in range(vecTable.GetNumberRows()):
1769                if imag:
1770                    vecGrid.SetCellStyle(row,3,VERY_LIGHT_GREY,True)                   
1771                for col in [4,5,6]:
1772                    vecGrid.SetCellStyle(row,col,VERY_LIGHT_GREY,True)
1773#            vecGrid.SetScrollRate(0,0)
1774            vecGrid.AutoSizeColumns(False)
1775            vecSizer.Add(vecGrid)
1776            return vecSizer
1777       
1778        def FillRefChoice(rbId,rbData):
1779            choiceIds = [i for i in range(len(rbData['rbTypes']))]
1780           
1781            rbRef = rbData.get('rbRef',[-1,-1,-1,False])
1782            for i in range(3):
1783                choiceIds.remove(rbRef[i])
1784            refChoice[rbId] = [choiceIds[:],choiceIds[:],choiceIds[:]]
1785            for i in range(3):
1786                refChoice[rbId][i].append(rbRef[i])
1787                refChoice[rbId][i].sort()     
1788           
1789        if VectorRB.GetSizer(): VectorRB.GetSizer().Clear(True)
1790        VectorRBSizer = wx.BoxSizer(wx.VERTICAL)
1791        for rbId in data['RBIds']['Vector']:
1792            if rbId != 'AtInfo':
1793                rbData = data['Vector'][rbId]
1794                FillRefChoice(rbId,rbData)
1795                VectorRBSizer.Add(rbNameSizer(rbId,rbData),0)
1796                VectorRBSizer.Add(rbRefAtmSizer(rbId,rbData),0)
1797                XYZ = np.array([[0.,0.,0.] for Ty in rbData['rbTypes']])
1798                for imag,mag in enumerate(rbData['VectMag']):
1799                    XYZ += mag*rbData['rbVect'][imag]
1800                    VectorRBSizer.Add(rbVectMag(rbId,imag,rbData),0)
1801                    VectorRBSizer.Add(rbVectors(rbId,imag,mag,XYZ,rbData),0)
1802                VectorRBSizer.Add((5,5),0)
1803                data['Vector'][rbId]['rbXYZ'] = XYZ       
1804        VectorRBSizer.Layout()   
1805        VectorRBDisplay.SetSizer(VectorRBSizer,True)
1806        Size = VectorRBSizer.GetMinSize()
1807        Size[0] += 40
1808        Size[1] = max(Size[1],450) + 20
1809        VectorRBDisplay.SetSize(Size)
1810        VectorRB.SetScrollbars(10,10,Size[0]/10-4,Size[1]/10-1)
1811        VectorRB.Scroll(0,Scroll)
1812       
1813    def UpdateResidueRB(rbId=0):
1814        AtInfo = data['Residue']['AtInfo']
1815        refChoice = {}
1816        RefObjs = []
1817
1818        def rbNameSizer(rbId,rbData):
1819
1820            def OnRBName(event):
1821                Obj = event.GetEventObject()
1822                rbData['RBname'] = Obj.GetValue()
1823                wx.CallAfter(UpdateResidueRB,rbId)
1824               
1825            def OnDelRB(event):
1826                Obj = event.GetEventObject()
1827                rbId = Indx[Obj.GetId()]
1828                if rbId in data['Residue']: 
1829                    del data['Residue'][rbId]
1830                    data['RBIds']['Residue'].remove(rbId)
1831                wx.CallAfter(UpdateResidueRB)
1832               
1833            def OnStripH(event):
1834                Obj = event.GetEventObject()
1835                rbId = Indx[Obj.GetId()]
1836                if rbId in data['Residue']:
1837                    newNames = []
1838                    newTypes = []
1839                    newXYZ = []
1840                    for i,atype in enumerate(rbData['rbTypes']):
1841                        if atype != 'H':
1842                            newNames.append(rbData['atNames'][i])
1843                            newTypes.append(rbData['rbTypes'][i])
1844                            newXYZ.append(rbData['rbXYZ'][i])
1845                    rbData['atNames'] = newNames
1846                    rbData['rbTypes'] = newTypes
1847                    rbData['rbXYZ'] = newXYZ
1848                G2plt.PlotRigidBody(G2frame,'Residue',AtInfo,rbData,plotDefaults)
1849                wx.CallAfter(UpdateResidueRB,rbId)
1850                   
1851            def OnPlotRB(event):
1852                Obj = event.GetEventObject()
1853                Obj.SetValue(False)
1854                G2plt.PlotRigidBody(G2frame,'Residue',AtInfo,rbData,plotDefaults)
1855           
1856            nameSizer = wx.BoxSizer(wx.HORIZONTAL)
1857            nameSizer.Add(wx.StaticText(ResidueRBDisplay,-1,'Residue name: '),
1858                0,wx.ALIGN_CENTER_VERTICAL)
1859            RBname = wx.TextCtrl(ResidueRBDisplay,-1,rbData['RBname'])
1860            Indx[RBname.GetId()] = rbId
1861            RBname.Bind(wx.EVT_TEXT_ENTER,OnRBName)
1862            RBname.Bind(wx.EVT_KILL_FOCUS,OnRBName)
1863            nameSizer.Add(RBname,0,wx.ALIGN_CENTER_VERTICAL)
1864            nameSizer.Add((5,0),)
1865            plotRB = wx.CheckBox(ResidueRBDisplay,-1,'Plot?')
1866            Indx[plotRB.GetId()] = rbId
1867            plotRB.Bind(wx.EVT_CHECKBOX,OnPlotRB)
1868            nameSizer.Add(plotRB,0,wx.ALIGN_CENTER_VERTICAL)
1869            nameSizer.Add((5,0),)
1870            if not rbData['useCount']:
1871                delRB = wx.CheckBox(ResidueRBDisplay,-1,'Delete?')
1872                Indx[delRB.GetId()] = rbId
1873                delRB.Bind(wx.EVT_CHECKBOX,OnDelRB)
1874                nameSizer.Add(delRB,0,wx.ALIGN_CENTER_VERTICAL)
1875                if 'H'  in rbData['rbTypes']:
1876                    stripH = wx.CheckBox(ResidueRBDisplay,-1,'Strip H-atoms?')
1877                    Indx[stripH.GetId()] = rbId
1878                    stripH.Bind(wx.EVT_CHECKBOX,OnStripH)
1879                    nameSizer.Add(stripH,0,wx.ALIGN_CENTER_VERTICAL)
1880            return nameSizer
1881           
1882        def rbResidues(rbId,rbData):
1883           
1884            def TypeSelect(event):
1885                AtInfo = data['Residue']['AtInfo']
1886                r,c = event.GetRow(),event.GetCol()
1887                if vecGrid.GetColLabelValue(c) == 'Type':
1888                    PE = G2elemGUI.PickElement(G2frame,oneOnly=True)
1889                    if PE.ShowModal() == wx.ID_OK:
1890                        if PE.Elem != 'None':
1891                            El = PE.Elem.strip().lower().capitalize()
1892                            if El not in AtInfo:
1893                                Info = G2elem.GetAtomInfo(El)
1894                                AtInfo[El] = [Info['Drad']['Color']]
1895                            rbData['rbTypes'][r] = El
1896                            vecGrid.SetCellValue(r,c,El)
1897                    PE.Destroy()
1898
1899            def ChangeCell(event):
1900                r,c =  event.GetRow(),event.GetCol()
1901                if r >= 0 and (0 <= c < 3):
1902                    try:
1903                        val = float(vecGrid.GetCellValue(r,c))
1904                        rbData['rbXYZ'][r][c] = val
1905                    except ValueError:
1906                        pass
1907                       
1908            def RowSelect(event):
1909                r,c =  event.GetRow(),event.GetCol()
1910                if c < 0:                   #only row clicks
1911                    for vecgrid in resList:
1912                        vecgrid.ClearSelection()
1913                    vecGrid.SelectRow(r,True)
1914
1915            def OnRefSel(event):
1916               
1917                Obj = event.GetEventObject()
1918                iref,res,jref = Indx[Obj.GetId()]
1919                sel = Obj.GetValue()
1920                ind = atNames.index(sel)
1921                if rbData['rbTypes'][ind] == 'H':
1922                    G2G.G2MessageBox(G2frame,'You should not select an H-atom for rigid body orientation')
1923                rbData['rbRef'][iref] = ind
1924                FillRefChoice(rbId,rbData)
1925                for i,ref in enumerate(RefObjs[jref]):
1926                    ref.SetItems([atNames[j] for j in refChoice[rbId][i]])
1927                    ref.SetValue(atNames[rbData['rbRef'][i]])                   
1928                rbXYZ = rbData['rbXYZ']
1929                if not iref:     #origin change
1930                    rbXYZ -= rbXYZ[ind]
1931                #TODO - transform all atom XYZ by axis choices
1932                Xxyz = rbXYZ[rbData['rbRef'][1]]
1933                X = Xxyz/np.sqrt(np.sum(Xxyz**2))
1934                Yxyz = rbXYZ[rbData['rbRef'][2]]
1935                Y = Yxyz/np.sqrt(np.sum(Yxyz**2))
1936                Mat = G2mth.getRBTransMat(X,Y)
1937                rbXYZ = np.inner(Mat,rbXYZ).T
1938                rbData['rbXYZ'] = rbXYZ
1939                res.ClearSelection()
1940                resTable = res.GetTable()
1941                for r in range(res.GetNumberRows()):
1942                    row = resTable.GetRowValues(r)
1943                    row[2:4] = rbXYZ[r]
1944                    resTable.SetRowValues(r,row)
1945                res.ForceRefresh()
1946                G2plt.PlotRigidBody(G2frame,'Residue',AtInfo,rbData,plotDefaults)
1947               
1948            Types = 2*[wg.GRID_VALUE_STRING,]+3*[wg.GRID_VALUE_FLOAT+':10,5',]
1949            colLabels = ['Name','Type','Cart x','Cart y','Cart z']
1950            table = []
1951            rowLabels = []
1952            for ivec,xyz in enumerate(rbData['rbXYZ']):
1953                table.append([rbData['atNames'][ivec],]+[rbData['rbTypes'][ivec],]+list(xyz))
1954                rowLabels.append(str(ivec))
1955            vecTable = G2G.Table(table,rowLabels=rowLabels,colLabels=colLabels,types=Types)
1956            vecGrid = G2G.GSGrid(ResidueRBDisplay)
1957            Indx[vecGrid.GetId()] = rbId
1958            resList.append(vecGrid)
1959            vecGrid.SetTable(vecTable, True)
1960            if 'phoenix' in wx.version():
1961                vecGrid.Bind(wg.EVT_GRID_CELL_CHANGED, ChangeCell)
1962            else:
1963                vecGrid.Bind(wg.EVT_GRID_CELL_CHANGE, ChangeCell)
1964            vecGrid.Bind(wg.EVT_GRID_CELL_LEFT_DCLICK, TypeSelect)
1965            vecGrid.Bind(wg.EVT_GRID_LABEL_LEFT_CLICK, RowSelect)
1966            attr = wx.grid.GridCellAttr()
1967            attr.SetEditor(G2G.GridFractionEditor(vecGrid))
1968            for c in range(3):
1969                vecGrid.SetColAttr(c, attr)
1970            for row in range(vecTable.GetNumberRows()):
1971                for col in range(5):
1972                    vecGrid.SetCellStyle(row,col,VERY_LIGHT_GREY,True)
1973            vecGrid.AutoSizeColumns(False)
1974            vecSizer = wx.BoxSizer()
1975            vecSizer.Add(vecGrid)
1976           
1977            refAtmSizer = wx.BoxSizer(wx.HORIZONTAL)
1978            atNames = rbData['atNames']
1979            rbRef = rbData['rbRef']
1980            if rbData['rbRef'][3] or rbData['useCount']:
1981                refAtmSizer.Add(wx.StaticText(ResidueRBDisplay,-1,
1982                    'Orientation reference non-H atoms A-B-C: %s, %s, %s'%(atNames[rbRef[0]], \
1983                     atNames[rbRef[1]],atNames[rbRef[2]])),0)
1984            else:
1985                refAtmSizer.Add(wx.StaticText(ResidueRBDisplay,-1,
1986                    'Orientation reference non-H atoms A-B-C: '),0,wx.ALIGN_CENTER_VERTICAL)
1987                refObj = [0,0,0]
1988                for i in range(3):
1989                    choices = [atNames[j] for j in refChoice[rbId][i]]
1990                    refSel = wx.ComboBox(ResidueRBDisplay,-1,value='',
1991                        choices=choices,style=wx.CB_READONLY|wx.CB_DROPDOWN)
1992                    refSel.SetValue(atNames[rbRef[i]])
1993                    refSel.Bind(wx.EVT_COMBOBOX, OnRefSel)
1994                    Indx[refSel.GetId()] = [i,vecGrid,len(RefObjs)]
1995                    refObj[i] = refSel
1996                    refAtmSizer.Add(refSel,0,wx.ALIGN_CENTER_VERTICAL)
1997                RefObjs.append(refObj)
1998           
1999            mainSizer = wx.BoxSizer(wx.VERTICAL)
2000            mainSizer.Add(refAtmSizer)
2001            mainSizer.Add(vecSizer)
2002            return mainSizer
2003           
2004        def SeqSizer(angSlide,rbId,iSeq,Seq,atNames):
2005           
2006            def ChangeAngle(event):
2007                event.Skip()
2008                Obj = event.GetEventObject()
2009                rbId,Seq = Indx[Obj.GetId()][:2]
2010                val = Seq[2]
2011                try:
2012                    val = float(Obj.GetValue())
2013                    Seq[2] = val
2014                except ValueError:
2015                    pass
2016                Obj.SetValue('%8.2f'%(val))
2017                G2plt.PlotRigidBody(G2frame,'Residue',AtInfo,data['Residue'][rbId],plotDefaults)
2018               
2019            def OnRadBtn(event):
2020                Obj = event.GetEventObject()
2021                Seq,iSeq,angId = Indx[Obj.GetId()]
2022                data['Residue'][rbId]['SelSeq'] = [iSeq,angId]
2023                angSlide.SetValue(int(100*Seq[2]))
2024               
2025            def OnDelBtn(event):
2026                Obj = event.GetEventObject()
2027                rbId,Seq = Indx[Obj.GetId()]
2028                data['Residue'][rbId]['rbSeq'].remove(Seq)       
2029                wx.CallAfter(UpdateResidueRB,rbId)
2030           
2031            seqSizer = wx.FlexGridSizer(0,5,2,2)
2032            seqSizer.AddGrowableCol(3,0)
2033            iBeg,iFin,angle,iMove = Seq
2034            ang = wx.TextCtrl(ResidueRBDisplay,-1,'%8.2f'%(angle),size=(50,20))
2035            if not iSeq:
2036                radBt = wx.RadioButton(ResidueRBDisplay,-1,'',style=wx.RB_GROUP)
2037                data['Residue'][rbId]['SelSeq'] = [iSeq,ang.GetId()]
2038            else:
2039                radBt = wx.RadioButton(ResidueRBDisplay,-1,'')
2040            radBt.Bind(wx.EVT_RADIOBUTTON,OnRadBtn)                   
2041            seqSizer.Add(radBt)
2042            delBt = wx.RadioButton(ResidueRBDisplay,-1,'')
2043            delBt.Bind(wx.EVT_RADIOBUTTON,OnDelBtn)
2044            seqSizer.Add(delBt)
2045            bond = wx.TextCtrl(ResidueRBDisplay,-1,'%s %s'%(atNames[iBeg],atNames[iFin]),size=(50,20))
2046            seqSizer.Add(bond,0,wx.ALIGN_CENTER_VERTICAL)
2047            Indx[radBt.GetId()] = [Seq,iSeq,ang.GetId()]
2048            Indx[delBt.GetId()] = [rbId,Seq]
2049            Indx[ang.GetId()] = [rbId,Seq,ang]
2050            ang.Bind(wx.EVT_TEXT_ENTER,ChangeAngle)
2051            ang.Bind(wx.EVT_KILL_FOCUS,ChangeAngle)
2052            seqSizer.Add(ang,0,wx.ALIGN_CENTER_VERTICAL)
2053            atms = ''
2054            for i in iMove:   
2055                atms += ' %s,'%(atNames[i])
2056            moves = wx.TextCtrl(ResidueRBDisplay,-1,atms[:-1],size=(200,20))
2057            seqSizer.Add(moves,1,wx.ALIGN_CENTER_VERTICAL|wx.EXPAND|wx.RIGHT)
2058            return seqSizer
2059           
2060        def SlideSizer():
2061           
2062            def OnSlider(event):
2063                Obj = event.GetEventObject()
2064                rbData = Indx[Obj.GetId()]
2065                iSeq,angId = rbData['SelSeq']
2066                val = float(Obj.GetValue())/100.
2067                rbData['rbSeq'][iSeq][2] = val
2068                Indx[angId][2].SetValue('%8.2f'%(val))
2069                G2plt.PlotRigidBody(G2frame,'Residue',AtInfo,rbData,plotDefaults)
2070           
2071            slideSizer = wx.BoxSizer(wx.HORIZONTAL)
2072            slideSizer.Add(wx.StaticText(ResidueRBDisplay,-1,'Selected torsion angle:'),0)
2073            iSeq,angId = rbData['SelSeq']
2074            angSlide = wx.Slider(ResidueRBDisplay,-1,
2075                int(100*rbData['rbSeq'][iSeq][2]),0,36000,size=(200,20),
2076                style=wx.SL_HORIZONTAL)
2077            angSlide.Bind(wx.EVT_SLIDER, OnSlider)
2078            Indx[angSlide.GetId()] = rbData
2079            slideSizer.Add(angSlide,0)           
2080            return slideSizer,angSlide
2081           
2082        def FillRefChoice(rbId,rbData):
2083            choiceIds = [i for i in range(len(rbData['atNames']))]
2084            for seq in rbData['rbSeq']:
2085                for i in seq[3]:
2086                    try:
2087                        choiceIds.remove(i)
2088                    except ValueError:
2089                        pass
2090            rbRef = rbData['rbRef']
2091            for i in range(3):
2092                try:
2093                    choiceIds.remove(rbRef[i])
2094                except ValueError:
2095                    pass
2096            refChoice[rbId] = [choiceIds[:],choiceIds[:],choiceIds[:]]
2097            for i in range(3):
2098                refChoice[rbId][i].append(rbRef[i])
2099                refChoice[rbId][i].sort()
2100               
2101        def OnSelect(event):
2102            rbname = rbchoice[select.GetSelection()]
2103            rbId = RBnames[rbname]
2104            wx.CallLater(100,UpdateResidueRB,rbId)
2105           
2106        if ResidueRBDisplay.GetSizer(): ResidueRBDisplay.GetSizer().Clear(True)
2107        RBnames = {}
2108        for rbid in data['RBIds']['Residue']:
2109            RBnames.update({data['Residue'][rbid]['RBname']:rbid,})
2110        rbchoice = RBnames.keys()
2111        ResidueRBSizer = wx.BoxSizer(wx.VERTICAL)
2112        if len(RBnames) > 1:
2113            selSizer = wx.BoxSizer(wx.HORIZONTAL)
2114            selSizer.Add(wx.StaticText(ResidueRBDisplay,label=' Select residue to view:'),0)
2115            rbchoice.sort()
2116            select = wx.ComboBox(ResidueRBDisplay,choices=rbchoice)
2117            select.Bind(wx.EVT_COMBOBOX,OnSelect)
2118            selSizer.Add(select,0)
2119            ResidueRBSizer.Add(selSizer,0)
2120        if not rbId:
2121            rbId = RBnames[rbchoice[0]]
2122        rbData = data['Residue'][rbId]
2123        FillRefChoice(rbId,rbData)
2124        ResidueRBSizer.Add(rbNameSizer(rbId,rbData),0)
2125        ResidueRBSizer.Add(rbResidues(rbId,rbData),0)
2126        ResidueRBSizer.Add((5,5),0)
2127        if rbData['rbSeq']:
2128            slideSizer,angSlide = SlideSizer()
2129        if len(rbData['rbSeq']):
2130            ResidueRBSizer.Add(wx.StaticText(ResidueRBDisplay,-1,
2131                'Sel  Del  Bond             Angle      Riding atoms'),
2132                0,wx.ALIGN_CENTER_VERTICAL)                       
2133        for iSeq,Seq in enumerate(rbData['rbSeq']):
2134            ResidueRBSizer.Add(SeqSizer(angSlide,rbId,iSeq,Seq,rbData['atNames']))
2135        if rbData['rbSeq']:
2136            ResidueRBSizer.Add(slideSizer,)
2137
2138        ResidueRBSizer.Add((5,25),)
2139        ResidueRBSizer.Layout()   
2140        ResidueRBDisplay.SetSizer(ResidueRBSizer,True)
2141        ResidueRBDisplay.SetAutoLayout(True)
2142        Size = ResidueRBSizer.GetMinSize()
2143        ResidueRBDisplay.SetSize(Size)
2144        ResidueRBDisplay.Show()
2145       
2146    def SetStatusLine(text):
2147        G2frame.GetStatusBar().SetStatusText(text,1)                                     
2148
2149    G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.RigidBodyMenu)
2150    SetStatusLine('')
2151    UpdateVectorRB()
2152    G2frame.rbBook.Bind(wx.aui.EVT_AUINOTEBOOK_PAGE_CHANGED, OnPageChanged)
2153    wx.CallAfter(OnPageChanged,None)
Note: See TracBrowser for help on using the repository browser.