source: trunk/GSASIIconstrGUI.py @ 3356

Last change on this file since 3356 was 3356, checked in by vondreele, 4 years ago

remove commented out SetTitle? & SetLabel? for data window from various places. Title is now gpx file name.
reconfigure restraints to show phase name subentries in tree - selection done from tree.
import phase, add phase & delete phase all handle restraints in new scheme
use of restraints unaffected as restraint data layout unchanged

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