source: trunk/GSASIIconstrGUI.py @ 3791

Last change on this file since 3791 was 3791, checked in by toby, 3 years ago

cleanup constraint display more

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