source: trunk/GSASIIconstrGUI.py @ 3789

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

fix transpose error for lattice constraints

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Author Revision URL Id
File size: 102.6 KB
Line 
1# -*- coding: utf-8 -*-
2#GSASIIconstrGUI - constraint GUI routines
3########### SVN repository information ###################
4# $Date: 2019-01-20 02:38:49 +0000 (Sun, 20 Jan 2019) $
5# $Author: toby $
6# $Revision: 3789 $
7# $URL: trunk/GSASIIconstrGUI.py $
8# $Id: GSASIIconstrGUI.py 3789 2019-01-20 02:38:49Z 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: 3789 $")
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                        eqString[-1] += '{:g}*{:} '.format(m,var)
816                        varMean = G2obj.fmtVarDescr(var)
817                        helptext += "\n" + var + " ("+ varMean + ")"
818                    if '_Explain' in data:
819                        if data['_Explain'].get(item[-3]):
820                            helptext += '\n\n'
821                            helptext += data['_Explain'][item[-3]]
822                    # typeString = 'NEWVAR'
823                    # if item[-3]:
824                    #     eqString[-1] += ' = '+item[-3]
825                    # else:
826                    #     eqString[-1] += ' = New Variable'
827                    if item[-3]:
828                        typeString = item[-3] + ' = '
829                    else:
830                        typeString = 'New Variable = '
831                    #print 'refine',item[-2]
832                    refineflag = True
833                elif item[-1] == 'c':
834                    helptext = "The following variables constrained to equal a constant:"
835                    for term in item[:-3]:
836                        var = str(term[1])
837                        if len(eqString[-1]) > maxlen:
838                            eqString.append(' ')
839                        m = term[0]
840                        if eqString[-1] != '':
841                            if term[0] > 0:
842                                eqString[-1] += ' + '
843                            else:
844                                eqString[-1] += ' - '
845                                m = -term[0]
846                        eqString[-1] += '{:g}*{:} '.format(m,var)
847                        varMean = G2obj.fmtVarDescr(var)
848                        helptext += "\n" + var + " ("+ varMean + ")"
849                    typeString = 'CONST'
850                    eqString[-1] += ' = '+str(item[-3])
851                elif item[-1] == 'e':
852                    helptext = "The following variables are set to be equivalent, noting multipliers:"
853                    normval = item[:-3][1][0]
854                    for i,term in enumerate(item[:-3]):
855                        var = str(term[1])
856                        if term[0] == 0: term[0] = 1.0
857                        if len(eqString[-1]) > maxlen:
858                            eqString.append(' ')
859                        if i == 0: # move independent variable to end
860                            indepterm = term
861                            continue
862                        elif eqString[-1] != '':
863                            eqString[-1] += ' = '
864                        if normval/term[0] == 1:
865                            eqString[-1] += '{:}'.format(var)
866                        else:
867                            eqString[-1] += '{:g}*{:} '.format(normval/term[0],var)
868                        varMean = G2obj.fmtVarDescr(var)
869                        helptext += "\n" + var + " ("+ varMean + ")"
870                    if normval/indepterm[0] == 1:
871                        eqString[-1] += ' = {:} '.format(indepterm[1])
872                    else:
873                        eqString[-1] += ' = {:g}*{:} '.format(normval/indepterm[0],str(indepterm[1]))
874                    typeString = 'EQUIV'
875                else:
876                    print ('Unexpected constraint'+item)
877               
878            else:
879                print ('Removing old-style constraints')
880                data[name] = []
881                return constSizer
882            constDel = wx.Button(pageDisplay,wx.ID_ANY,'Delete',style=wx.BU_EXACTFIT)
883            constDel.Bind(wx.EVT_BUTTON,OnConstDel)
884            Indx[constDel.GetId()] = [Id,name]
885            constSizer.Add(constDel,0,wx.LEFT|wx.RIGHT|wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER,1)             # delete button
886            if helptext:
887                ch = G2G.HelpButton(pageDisplay,helptext)
888                constSizer.Add(ch,0,wx.LEFT|wx.RIGHT|wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER,1)
889            else:
890                constSizer.Add((-1,-1))
891            if refineflag:
892                ch = G2G.G2CheckBox(pageDisplay,'',item,-2)
893                constSizer.Add(ch,0,wx.LEFT|wx.RIGHT|wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER,1)
894            else:
895                constSizer.Add((-1,-1))               
896            constSizer.Add(wx.StaticText(pageDisplay,wx.ID_ANY,typeString),
897                           0,wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER|wx.RIGHT|wx.LEFT,3)
898            if problemItem: eqString[-1] += ' -- Conflict: see console'
899            if len(eqString) > 1:
900                Eq = wx.BoxSizer(wx.VERTICAL)
901                for s in eqString:
902                    line = wx.StaticText(pageDisplay,wx.ID_ANY,s)
903                    if problemItem: line.SetBackgroundColour(wx.YELLOW)
904                    Eq.Add(line,0,wx.ALIGN_CENTER_VERTICAL)
905                Eq.Add((-1,4))
906            else:
907                Eq = wx.StaticText(pageDisplay,wx.ID_ANY,eqString[0])
908                if problemItem: Eq.SetBackgroundColour(wx.YELLOW)
909            constSizer.Add(Eq,1,wx.ALIGN_CENTER_VERTICAL)
910        return constSizer
911               
912    def OnConstDel(event):
913        'Delete a constraint'
914        Obj = event.GetEventObject()
915        Id,name = Indx[Obj.GetId()]
916        del data[name][Id]
917        ConstraintsLoad(data)
918        wx.CallAfter(OnPageChanged,None)
919
920    def OnConstEdit(event):
921        '''Called to edit an individual contraint in response to a
922        click on its Edit button
923        '''
924        Obj = event.GetEventObject()
925        Id,name = Indx[Obj.GetId()]
926        if data[name][Id][-1] == 'f':
927            items = data[name][Id][:-3]
928            constType = 'New Variable'
929            if data[name][Id][-3]:
930                varname = data[name][Id][-3]
931            else:
932                varname = ""
933            lbl = 'Enter value for each term in constraint; sum = new variable'
934            dlg = ConstraintDialog(G2frame,constType,lbl,items,
935                                   varname=varname,varyflag=data[name][Id][-2])
936        elif data[name][Id][-1] == 'c':
937            items = data[name][Id][:-3]+[
938                [data[name][Id][-3],'fixed value =']]
939            constType = 'Constraint'
940            lbl = 'Edit value for each term in constant constraint sum'
941            dlg = ConstraintDialog(G2frame,constType,lbl,items)
942        elif data[name][Id][-1] == 'e':
943            items = data[name][Id][:-3]
944            constType = 'Equivalence'
945            lbl = 'The following terms are set to be equal:'
946            dlg = ConstraintDialog(G2frame,constType,lbl,items,'/')
947        else:
948            return
949        try:
950            prev = data[name][Id][:]
951            if dlg.ShowModal() == wx.ID_OK:
952                result = dlg.GetData()
953                for i in range(len(data[name][Id][:-3])):
954                    if type(data[name][Id][i]) is tuple: # fix non-mutable construct
955                        data[name][Id][i] = list(data[name][Id][i])
956                    data[name][Id][i][0] = result[i][0]
957                if data[name][Id][-1] == 'c':
958                    data[name][Id][-3] = str(result[-1][0])
959                elif data[name][Id][-1] == 'f':
960                    # process the variable name to put in global form (::var)
961                    varname = str(dlg.newvar[0]).strip().replace(' ','_')
962                    if varname.startswith('::'):
963                        varname = varname[2:]
964                    varname = varname.replace(':',';')
965                    if varname:
966                        data[name][Id][-3] = varname
967                    else:
968                        data[name][Id][-3] = ''
969                    data[name][Id][-2] = dlg.newvar[1]
970                if not CheckChangedConstraint():
971                    data[name][Id] = prev
972        except:
973            import traceback
974            print (traceback.format_exc())
975        finally:
976            dlg.Destroy()
977        wx.CallAfter(OnPageChanged,None)
978   
979    def UpdateConstraintPanel(panel,typ):
980        '''Update the contents of the selected Constraint
981        notebook tab. Called in :func:`OnPageChanged`
982        '''
983        if panel.GetSizer(): panel.GetSizer().Clear(True)
984        Siz = wx.BoxSizer(wx.VERTICAL)
985        Siz.Add((5,5),0)
986        Siz.Add(MakeConstraintsSizer(typ,panel),1,wx.EXPAND)
987        panel.SetSizer(Siz,True)
988        Size = Siz.GetMinSize()
989        Size[0] += 40
990        Size[1] = max(Size[1],450) + 20
991        panel.SetSize(Size)
992        panel.SetScrollbars(10,10,Size[0]/10-4,Size[1]/10-1)
993        panel.Show()
994
995    def OnPageChanged(event):
996        '''Called when a tab is pressed or when a "select tab" menu button is
997        used (see RaisePage), or to refresh the current tab contents (event=None)
998        '''
999        if event:       #page change event!
1000            page = event.GetSelection()
1001        else: # called directly, get current page
1002            page = G2frame.constr.GetSelection()
1003        G2frame.constr.ChangeSelection(page)
1004        text = G2frame.constr.GetPageText(page)
1005        G2frame.dataWindow.ConstraintEdit.Enable(G2G.wxID_EQUIVALANCEATOMS,False)
1006#        G2frame.dataWindow.ConstraintEdit.Enable(G2G.wxID_ADDRIDING,False)
1007        if text == 'Histogram/Phase':
1008            enableEditCons = [False]+4*[True]
1009            G2frame.Page = [page,'hap']
1010            UpdateConstraintPanel(HAPConstr,'HAP')
1011        elif text == 'Histogram':
1012            enableEditCons = [False]+4*[True]
1013            G2frame.Page = [page,'hst']
1014            UpdateConstraintPanel(HistConstr,'Hist')
1015        elif text == 'Phase':
1016            enableEditCons = 5*[True]
1017            G2frame.Page = [page,'phs']
1018            G2frame.dataWindow.ConstraintEdit.Enable(G2G.wxID_EQUIVALANCEATOMS,True)
1019#            G2frame.dataWindow.ConstraintEdit.Enable(G2G.wxID_ADDRIDING,True)
1020            if 'DELETED' in str(PhaseConstr):   #seems to be no other way to do this (wx bug)
1021                if GSASIIpath.GetConfigValue('debug'):
1022                    print ('DBG_wx error: PhaseConstr not cleanly deleted after Refine')
1023                return
1024            UpdateConstraintPanel(PhaseConstr,'Phase')
1025        elif text == 'Global':
1026            enableEditCons = [False]+4*[True]
1027            G2frame.Page = [page,'glb']
1028            UpdateConstraintPanel(GlobalConstr,'Global')
1029        else:
1030            enableEditCons = 5*[False]
1031            G2frame.Page = [page,'sym']
1032            UpdateConstraintPanel(SymConstr,'Sym-Generated')
1033        # remove menu items when not allowed
1034        for obj,flag in zip(G2frame.dataWindow.ConstraintEdit.GetMenuItems(),enableEditCons): 
1035            obj.Enable(flag)
1036        G2frame.dataWindow.SetDataSize()
1037
1038    def RaisePage(event):
1039        'Respond to a "select tab" menu button'
1040        try:
1041            i = (G2G.wxID_CONSPHASE,
1042                 G2G.wxID_CONSHAP,
1043                 G2G.wxID_CONSHIST,
1044                 G2G.wxID_CONSGLOBAL,
1045                 G2G.wxID_CONSSYM,
1046                ).index(event.GetId())
1047            G2frame.constr.SetSelection(i)
1048            wx.CallAfter(OnPageChanged,None)
1049        except ValueError:
1050            print('Unexpected event in RaisePage')
1051
1052    def SetStatusLine(text):
1053        G2frame.GetStatusBar().SetStatusText(text,1)
1054
1055    # UpdateConstraints execution starts here
1056    if not data:
1057        data.update({'Hist':[],'HAP':[],'Phase':[],'Global':[]})       #empty dict - fill it
1058    if 'Global' not in data:                                            #patch
1059        data['Global'] = []
1060    # DEBUG code ########################################
1061    #import GSASIIconstrGUI
1062    #reload(GSASIIconstrGUI)
1063    #reload(G2obj)
1064    #reload(G2stIO)
1065    #import GSASIIstrMain
1066    #reload(GSASIIstrMain)   
1067    #reload(G2mv)
1068    #reload(G2gd)
1069    ###################################################
1070    Histograms,Phases = G2frame.GetUsedHistogramsAndPhasesfromTree()
1071    if not len(Phases) or not len(Histograms):
1072        dlg = wx.MessageDialog(G2frame,'You need both phases and histograms to see Constraints',
1073            'No phases or histograms')
1074        dlg.CenterOnParent()
1075        dlg.ShowModal()
1076        dlg.Destroy()
1077        return
1078    G2obj.IndexAllIds(Histograms,Phases)
1079    ##################################################################################
1080    # patch: convert old-style (str) variables in constraints to G2VarObj objects
1081    for key,value in data.items():
1082        if key.startswith('_'): continue
1083        j = 0
1084        for cons in value:
1085            #print cons             # DEBUG
1086            for i in range(len(cons[:-3])):
1087                if type(cons[i][1]) is str:
1088                    cons[i][1] = G2obj.G2VarObj(cons[i][1])
1089                    j += 1
1090        if j:
1091            print (str(key) + ': '+str(j)+' variable(s) as strings converted to objects')
1092    ##################################################################################
1093    rigidbodyDict = G2frame.GPXtree.GetItemPyData(
1094        G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'Rigid bodies'))
1095    rbIds = rigidbodyDict.get('RBIds',{'Vector':[],'Residue':[]})
1096    rbVary,rbDict = G2stIO.GetRigidBodyModels(rigidbodyDict,Print=False)
1097    badPhaseParms = ['Ax','Ay','Az','Amul','AI/A','Atype','SHorder','mV0','mV1','mV2','AwaveType','FwaveType','PwaveType','MwaveType','Vol','isMag',]
1098    globalList = list(rbDict.keys())
1099    globalList.sort()
1100    try:
1101        AtomDict = dict([Phases[phase]['pId'],Phases[phase]['Atoms']] for phase in Phases)
1102    except KeyError:
1103        G2frame.ErrorDialog('Constraint Error','Constraints cannot be set until a cycle of least squares'+
1104                            ' has been run.\nWe suggest you refine a scale factor.')
1105        return
1106
1107    # create a list of the phase variables
1108    Natoms,atomIndx,phaseVary,phaseDict,pawleyLookup,FFtable,BLtable,MFtable,maxSSwave = G2stIO.GetPhaseData(Phases,rbIds=rbIds,Print=False)
1109    phaseList = []
1110    for item in phaseDict:
1111        if item.split(':')[2] not in badPhaseParms:
1112            phaseList.append(item)
1113    phaseList.sort()
1114    phaseAtNames = {}
1115    phaseAtTypes = {}
1116    TypeList = []
1117    for item in phaseList:
1118        Split = item.split(':')
1119        if Split[2][:2] in ['AU','Af','dA','AM']:
1120            Id = int(Split[0])
1121            phaseAtNames[item] = AtomDict[Id][int(Split[3])][0]
1122            phaseAtTypes[item] = AtomDict[Id][int(Split[3])][1]
1123            if phaseAtTypes[item] not in TypeList:
1124                TypeList.append(phaseAtTypes[item])
1125        else:
1126            phaseAtNames[item] = ''
1127            phaseAtTypes[item] = ''
1128             
1129    # create a list of the hist*phase variables
1130    seqList = G2frame.testSeqRefineMode()
1131    if seqList: # for sequential refinement, only process 1st histgram in list
1132        histDict = {seqList[0]:Histograms[seqList[0]]}
1133    else:
1134        histDict = Histograms
1135    hapVary,hapDict,controlDict = G2stIO.GetHistogramPhaseData(Phases,histDict,Print=False,resetRefList=False)
1136    hapList = sorted([i for i in hapDict.keys() if i.split(':')[2] not in ('Type',)])
1137    if seqList: # convert histogram # to wildcard
1138        wildList = [] # list of variables with "*" for histogram number
1139        for i in hapList:
1140            s = i.split(':')
1141            if s[1] == "": continue
1142            s[1] = '*'
1143            sj = ':'.join(s)
1144            if sj not in wildList: wildList.append(sj)
1145        hapList = wildList
1146    histVary,histDict,controlDict = G2stIO.GetHistogramData(histDict,Print=False)
1147    histList = list(histDict.keys())
1148    histList.sort()
1149    if seqList: # convert histogram # to wildcard
1150        wildList = [] # list of variables with "*" for histogram number
1151        for i in histList:
1152            s = i.split(':')
1153            if s[1] == "": continue
1154            s[1] = '*'
1155            sj = ':'.join(s)
1156            if sj not in wildList: wildList.append(sj)
1157        histList = wildList       
1158    Indx = {}
1159    G2frame.Page = [0,'phs']
1160   
1161    G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.ConstraintMenu)
1162    SetStatusLine('')
1163   
1164    G2frame.Bind(wx.EVT_MENU, OnAddConstraint, id=G2G.wxID_CONSTRAINTADD)
1165    G2frame.Bind(wx.EVT_MENU, OnAddFunction, id=G2G.wxID_FUNCTADD)
1166    G2frame.Bind(wx.EVT_MENU, OnAddEquivalence, id=G2G.wxID_EQUIVADD)
1167    G2frame.Bind(wx.EVT_MENU, OnAddHold, id=G2G.wxID_HOLDADD)
1168    G2frame.Bind(wx.EVT_MENU, OnAddAtomEquiv, id=G2G.wxID_EQUIVALANCEATOMS)
1169#    G2frame.Bind(wx.EVT_MENU, OnAddRiding, id=G2G.wxID_ADDRIDING)
1170    # tab commands
1171    for id in (G2G.wxID_CONSPHASE,
1172               G2G.wxID_CONSHAP,
1173               G2G.wxID_CONSHIST,
1174               G2G.wxID_CONSGLOBAL,
1175               G2G.wxID_CONSSYM,
1176               ):
1177        G2frame.Bind(wx.EVT_MENU, RaisePage,id=id)
1178
1179    #G2frame.constr = G2G.GSNoteBook(parent=G2frame.dataWindow,size=G2frame.dataWindow.GetClientSize())
1180    G2frame.constr = G2G.GSNoteBook(parent=G2frame.dataWindow)
1181    G2frame.dataWindow.GetSizer().Add(G2frame.constr,1,wx.ALL|wx.EXPAND)
1182    # note that order of pages is hard-coded in RaisePage
1183    PhaseConstr = wx.ScrolledWindow(G2frame.constr)
1184    G2frame.constr.AddPage(PhaseConstr,'Phase')
1185    HAPConstr = wx.ScrolledWindow(G2frame.constr)
1186    G2frame.constr.AddPage(HAPConstr,'Histogram/Phase')
1187    HistConstr = wx.ScrolledWindow(G2frame.constr)
1188    G2frame.constr.AddPage(HistConstr,'Histogram')
1189    GlobalConstr = wx.ScrolledWindow(G2frame.constr)
1190    G2frame.constr.AddPage(GlobalConstr,'Global')
1191    SymConstr = wx.ScrolledWindow(G2frame.constr)
1192    G2frame.constr.AddPage(SymConstr,'Sym-Generated')
1193    wx.CallAfter(OnPageChanged,None)
1194    G2frame.constr.Bind(wx.aui.EVT_AUINOTEBOOK_PAGE_CHANGED, OnPageChanged)
1195    # validate all the constrants -- should not see any errors here normally
1196    errmsg,warnmsg = ConstraintsCheck(data)
1197    if errmsg:
1198        G2frame.ErrorDialog('Constraint Error',
1199                            'Error in constraints:\n'+errmsg+'\nCheck console output for more information',
1200                            parent=G2frame)
1201        print (errmsg)
1202        print (G2mv.VarRemapShow([],True))
1203    elif warnmsg:
1204        print ('Unexpected contraint warning:\n'+warnmsg)
1205       
1206################################################################################
1207# check scale & phase fractions, create constraint if needed
1208################################################################################
1209def CheckAllScalePhaseFractions(G2frame):
1210    '''Check if scale factor and all phase fractions are refined without a constraint
1211    for all used histograms, if so, offer the user a chance to create a constraint
1212    on the sum of phase fractions
1213    '''
1214    histograms, phases = G2frame.GetUsedHistogramsAndPhasesfromTree()
1215    for i,hist in enumerate(histograms):
1216        CheckScalePhaseFractions(G2frame,hist,histograms,phases)
1217       
1218def CheckScalePhaseFractions(G2frame,hist,histograms,phases):
1219    '''Check if scale factor and all phase fractions are refined without a constraint
1220    for histogram hist, if so, offer the user a chance to create a constraint
1221    on the sum of phase fractions
1222    '''
1223    if G2frame.testSeqRefineMode():
1224        histStr = '*'
1225    else:
1226        histStr = str(histograms[hist]['hId'])
1227    # Is this powder?
1228    if not hist.startswith('PWDR '): return
1229    # do this only if the scale factor is varied
1230    if not histograms[hist]['Sample Parameters']['Scale'][1]: return
1231    # are all phase fractions varied in all used histograms?
1232    phaseCount = 0
1233    for p in phases:
1234        if hist not in phases[p]['Histograms']: continue
1235        if phases[p]['Histograms'][hist]['Use'] and not phases[p]['Histograms'][hist]['Scale'][1]:
1236            return
1237        else:
1238            phaseCount += 1
1239   
1240    # all phase fractions and scale factor varied, now scan through constraints
1241    sub = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'Constraints') 
1242    Constraints = G2frame.GPXtree.GetItemPyData(sub)
1243    for c in Constraints.get('HAP',[]):
1244        if c[-1] != 'c': continue
1245        if not c[-3]: continue
1246        if len(c[:-3]) != phaseCount: continue
1247        # got a constraint equation with right number of terms, is it on phase fractions for
1248        # the correct histogram?
1249        if all([(i[1].name == 'Scale' and i[1].varname().split(':')[1] == histStr) for i in c[:-3]]):
1250            # got a constraint, this is OK
1251            return
1252    dlg = wx.MessageDialog(G2frame,'You are refining the scale factor and all phase fractions for histogram #'+
1253        histStr+'. This will produce an unstable refinement. '+
1254        'Do you want to constrain the sum of phase fractions?','Create constraint?',wx.OK|wx.CANCEL)
1255    if dlg.ShowModal() != wx.ID_OK:
1256        dlg.Destroy()
1257        return
1258    dlg.Destroy()
1259
1260    constr = []
1261    for p in phases:
1262        if hist not in phases[p]['Histograms']: continue
1263        if not phases[p]['Histograms'][hist]['Use']: continue
1264        constr += [[1.0,G2obj.G2VarObj(':'.join((str(phases[p]['pId']),histStr,'Scale')))]]
1265    constr += [1.0,None,'c']
1266    Constraints['HAP'] += [constr]
1267       
1268################################################################################
1269#### Make nuclear/magnetic phase transition constraints - called by OnTransform in G2phsGUI
1270################################################################################       
1271       
1272def TransConstraints(G2frame,oldPhase,newPhase,Trans,Vec,atCodes):
1273    '''Add constraints for new magnetic phase created via transformation of old
1274    nuclear one
1275    NB: A = [G11,G22,G33,2*G12,2*G13,2*G23]
1276    '''
1277   
1278    def SetUniqAj(pId,iA,SGData):
1279        SGLaue = SGData['SGLaue']
1280        SGUniq = SGData['SGUniq']
1281        if SGLaue in ['m3','m3m']:
1282            if iA in [0,1,2]:
1283                parm = '%d::%s'%(pId,'A0')
1284            else:
1285                parm = None
1286        elif SGLaue in ['4/m','4/mmm']:
1287            if iA in [0,1]:
1288                parm = '%d::%s'%(pId,'A0')
1289            elif iA == 2:
1290                parm = '%d::%s'%(pId,'A2')
1291            else:
1292                parm = None
1293        elif SGLaue in ['6/m','6/mmm','3m1', '31m', '3']:
1294            if iA in [0,1,3]:
1295                parm = '%d::%s'%(pId,'A0')
1296            elif iA == 2:
1297                parm = '%d::%s'%(pId,'A2')
1298            else:
1299                parm = None
1300        elif SGLaue in ['3R', '3mR']:
1301            if ia in [0,1,2]:
1302                parm = '%d::%s'%(pId,'A0')
1303            else:
1304                parm = '%d::%s'%(pId,'A3')
1305        elif SGLaue in ['mmm',]:
1306            if iA in [0,1,2]:
1307                parm = '%d::A%s'%(pId,iA)
1308            else:
1309                parm = None
1310        elif SGLaue == '2/m':
1311            if iA in [0,1,2]:
1312                parm = '%d::A%s'%(pId,iA)
1313            elif iA == 3 and SGUniq == 'c':
1314                parm = '%d::A%s'%(pId,iA)
1315            elif iA == 4 and SGUniq == 'b':
1316                parm = '%d::A%s'%(pId,iA)
1317            elif iA == 5 and SGUniq == 'a':
1318                parm = '%d::A%s'%(pId,iA)
1319            else:
1320                parm = None           
1321        else:
1322            parm = '%d::A%s'%(pId,iA)
1323        return parm
1324   
1325    Histograms,Phases = G2frame.GetUsedHistogramsAndPhasesfromTree()
1326    UseList = newPhase['Histograms']
1327    detTrans = np.abs(nl.det(Trans))
1328    opId = oldPhase['pId']
1329    npId = newPhase['pId']
1330    cx,ct,cs,cia = newPhase['General']['AtomPtrs']
1331    nAtoms = newPhase['Atoms']
1332    oSGData = oldPhase['General']['SGData']
1333    nSGData = newPhase['General']['SGData']
1334    #oAcof = G2lat.cell2A(oldPhase['General']['Cell'][1:7])
1335    #nAcof = G2lat.cell2A(newPhase['General']['Cell'][1:7])
1336    item = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'Constraints')
1337    if not item:
1338        print('Error: no constraints in Data Tree')
1339        return
1340    constraints = G2frame.GPXtree.GetItemPyData(item)
1341    xnames = ['dAx','dAy','dAz']
1342    # constraints on matching atom params between phases
1343    for ia,code in enumerate(atCodes):
1344        atom = nAtoms[ia]
1345        if not ia and atom[cia] == 'A':
1346            wx.MessageDialog(G2frame,
1347                'Anisotropic thermal motion constraints are not developed at the present time',
1348                'Anisotropic thermal constraint?',style=wx.ICON_INFORMATION).ShowModal()
1349        siteSym = G2spc.SytSym(atom[cx:cx+3],nSGData)[0]
1350        CSX = G2spc.GetCSxinel(siteSym)
1351#        CSU = G2spc.GetCSuinel(siteSym)
1352        item = code.split('+')[0]
1353        iat,opr = item.split(':')
1354        Nop = abs(int(opr))%100-1
1355        if '-' in opr:
1356            Nop *= -1
1357        Opr = oldPhase['General']['SGData']['SGOps'][abs(Nop)][0]
1358        if Nop < 0:         #inversion
1359            Opr *= -1
1360        XOpr = np.inner(Opr,Trans)
1361        for ix in list(set(CSX[0])):
1362            if not ix:
1363                continue
1364            name = xnames[ix-1]
1365            IndpCon = [1.0,G2obj.G2VarObj('%d::%s:%d'%(npId,name,ia))]
1366            DepCons = []
1367            for iop,opval in enumerate(XOpr[ix-1]):
1368                if opval:
1369                    DepCons.append([opval,G2obj.G2VarObj('%d::%s:%s'%(opId,xnames[iop],iat))])
1370            if len(DepCons) == 1:
1371                constraints['Phase'].append([DepCons[0],IndpCon,None,None,'e'])
1372            elif len(DepCons) > 1:
1373                for Dep in DepCons:
1374                    Dep[0] *= -1
1375                constraints['Phase'].append([IndpCon]+DepCons+[0.0,None,'c'])
1376        for name in ['Afrac','AUiso']:
1377            IndpCon = [1.0,G2obj.G2VarObj('%d::%s:%d'%(npId,name,ia))]
1378            DepCons = [1.0,G2obj.G2VarObj('%d::%s:%s'%(opId,name,iat))]
1379            constraints['Phase'].append([DepCons,IndpCon,None,None,'e'])
1380           
1381        # unfinished Anisotropic constraint generation
1382#        Uids = [[0,0,'AU11'],[1,1,'AU22'],[2,2,'AU33'],[0,1,'AU12'],[0,2,'AU13'],[1,2,'AU23']]
1383#        DepConsDict = dict(zip(Us,[[],[],[],[],[],[]]))
1384#        for iu,Uid in enumerate(Uids):
1385#            UMT = np.zeros((3,3))
1386#            UMT[Uid[0],Uid[1]] = 1
1387#            nUMT = G2lat.prodMGMT(UMT,invTrans)
1388#            nUT = G2lat.UijtoU6(nUMT)
1389#            for iu,nU in enumerate(nUT):
1390#                if abs(nU) > 1.e-8:
1391#                    parm = '%d::%s;%s'%(opId,Us[iu],iat)
1392#                    DepConsDict[Uid[2]].append([abs(nU%1.),G2obj.G2VarObj(parm)])
1393#        nUcof = atom[iu:iu+6]
1394#        conStrings = []
1395#        for iU,Usi in enumerate(Us):
1396#            parm = '%d::%s;%d'%(npId,Usi,ia)
1397#            parmDict[parm] = nUcof[iU]
1398#            varyList.append(parm)
1399#            IndpCon = [1.0,G2obj.G2VarObj(parm)]
1400#            conStr = str([IndpCon,DepConsDict[Usi]])
1401#            if conStr in conStrings:
1402#                continue
1403#            conStrings.append(conStr)
1404#            if len(DepConsDict[Usi]) == 1:
1405#                if DepConsDict[Usi][0]:
1406#                    constraints['Phase'].append([IndpCon,DepConsDict[Usi][0],None,None,'e'])
1407#            elif len(DepConsDict[Usi]) > 1:       
1408#                for Dep in DepConsDict[Usi]:
1409#                    Dep[0] *= -1
1410#                constraints['Phase'].append([IndpCon]+DepConsDict[Usi]+[0.0,None,'c'])
1411           
1412        #how do I do Uij's for most Trans?
1413       
1414    T = nl.inv(Trans).T
1415    conMat = [
1416        [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]],
1417        [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]],
1418        [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]],
1419        [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]],
1420        [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]],
1421        [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]]
1422        ]
1423    # Gnew = conMat * A:
1424#         T00**2*a0  T01**2*a1 T02**2*a2 T00*T01*a3    T00*T02*a4    T01*T02*a5
1425#         T10**2*a0  T11**2*a1 T12**2*a2 T10*T11*a3    T10*T12*a4    T11*T12*a5
1426#         T20**2*a0  T21**2*a1 T22**2*a2 T20*T21*a3    T20*T22*a4    T21*T22*a5
1427#         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
1428#         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
1429#         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
1430    # Generated as symbolic code using:
1431    # import sym
1432    # A0, A1, A2, A3, A4, A5 = sym.symbols('A0, A1, A2, A3, A4, A5')
1433    # G = sym.Matrix([ [A0,    A3/2.,  A4/2.], [A3/2.,  A1,    A5/2.], [A4/2., A5/2.,    A2]])
1434    # transformation matrix
1435    # T00, T10, T20, T01, T11, T21, T02, T12, T22 = sym.symbols('T00, T10, T20, T01, T11, T21, T02, T12, T22')
1436    # Tr = sym.Matrix([ [T00, T10, T20], [T01, T11, T21], [T02, T12, T22],])
1437    # Gnew = (Tr.T*G)*Tr
1438   
1439    for iAnew,Asi in enumerate(['A0','A1','A2','A3','A4','A5']): # loop through A[i] for new cell
1440        Nparm = str(npId) + '::' + Asi
1441        if Nparm != SetUniqAj(npId,iAnew,nSGData): continue # skip if already constrainted
1442        multDict = {}
1443        for iAorg in range(6):
1444            cA = conMat[iAnew][iAorg] # coeff for A[i] in constraint matrix
1445            if abs(cA) < 1.e-8: continue
1446            parm = SetUniqAj(opId,iAorg,oSGData) # translate to unique A[i] in original cell
1447            if not parm: continue
1448            # sum coeff
1449            if parm in multDict:
1450                multDict[parm] += cA
1451            else:
1452                multDict[parm] = cA
1453        # any non-zero multipliers?
1454        maxMult = 0
1455        for i in multDict:
1456            maxMult = max(maxMult,abs(multDict[i]))
1457        if maxMult <= 0: continue
1458           
1459        # create constraint (if needed) or equivalence
1460        if len(multDict) == 1:
1461            key = list(multDict.keys())[0]
1462            constr = [
1463                [1.0,G2obj.G2VarObj(key)],
1464                [multDict[key],G2obj.G2VarObj(Nparm)],
1465                None,None,'e']
1466        else:
1467            constr = [[-1.0,G2obj.G2VarObj(Nparm)]]
1468            for key in multDict:
1469                constr += [[multDict[key],G2obj.G2VarObj(key)]]
1470            constr += [0.0,None,'c']
1471        constraints['Phase'] += [constr]
1472   
1473    # constraints on HAP Scale, etc.
1474    for hId,hist in enumerate(UseList):    #HAP - seems OK
1475        ohapkey = '%d:%d:'%(opId,hId)
1476        nhapkey = '%d:%d:'%(npId,hId)
1477        IndpCon = [1.0,G2obj.G2VarObj(ohapkey+'Scale')]
1478        DepCons = [detTrans,G2obj.G2VarObj(nhapkey+'Scale')]
1479        constraints['HAP'].append([DepCons,IndpCon,None,None,'e'])
1480        for name in ['Size;i','Mustrain;i']:
1481            IndpCon = [1.0,G2obj.G2VarObj(ohapkey+name)]
1482            DepCons = [1.0,G2obj.G2VarObj(nhapkey+name)]
1483            constraints['HAP'].append([IndpCon,DepCons,None,None,'e'])
1484       
1485################################################################################
1486#### Rigid bodies
1487################################################################################
1488
1489def UpdateRigidBodies(G2frame,data):
1490    '''Called when Rigid bodies tree item is selected.
1491    Displays the rigid bodies in the data window
1492    '''
1493    if not data.get('RBIds') or not data:
1494        data.update({'Vector':{'AtInfo':{}},'Residue':{'AtInfo':{}},
1495            'RBIds':{'Vector':[],'Residue':[]}})       #empty/bad dict - fill it
1496           
1497    global resList,rbId
1498    Indx = {}
1499    resList = []
1500    plotDefaults = {'oldxy':[0.,0.],'Quaternion':[0.,0.,0.,1.],'cameraPos':30.,'viewDir':[0,0,1],}
1501
1502    G2frame.rbBook = G2G.GSNoteBook(parent=G2frame.dataWindow)
1503    G2frame.dataWindow.GetSizer().Add(G2frame.rbBook,1,wx.ALL|wx.EXPAND)
1504    VectorRB = wx.ScrolledWindow(G2frame.rbBook)
1505    VectorRBDisplay = wx.Panel(VectorRB)
1506    G2frame.rbBook.AddPage(VectorRB,'Vector rigid bodies')
1507    ResidueRB = wx.ScrolledWindow(G2frame.rbBook)
1508    ResidueRBDisplay = wx.Panel(ResidueRB)
1509    G2frame.rbBook.AddPage(ResidueRB,'Residue rigid bodies')
1510   
1511    def OnPageChanged(event):
1512        global resList
1513        resList = []
1514        if event:       #page change event!
1515            page = event.GetSelection()
1516        else:
1517            page = G2frame.rbBook.GetSelection()
1518        G2frame.rbBook.ChangeSelection(page)
1519        text = G2frame.rbBook.GetPageText(page)
1520        if text == 'Vector rigid bodies':
1521            G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.VectorBodyMenu)
1522            G2frame.Bind(wx.EVT_MENU, AddVectorRB, id=G2G.wxID_VECTORBODYADD)
1523            G2frame.Page = [page,'vrb']
1524            UpdateVectorRB()
1525        elif text == 'Residue rigid bodies':
1526            G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.RigidBodyMenu)
1527            G2frame.Bind(wx.EVT_MENU, AddResidueRB, id=G2G.wxID_RIGIDBODYADD)
1528            G2frame.Bind(wx.EVT_MENU, OnImportRigidBody, id=G2G.wxID_RIGIDBODYIMPORT)
1529            G2frame.Bind(wx.EVT_MENU, OnDefineTorsSeq, id=G2G.wxID_RESIDUETORSSEQ) #enable only if residue RBs exist?
1530            G2frame.Page = [page,'rrb']
1531            UpdateResidueRB()
1532           
1533    def getMacroFile(macName):
1534        defDir = os.path.join(os.path.split(__file__)[0],'GSASIImacros')
1535        dlg = wx.FileDialog(G2frame,message='Choose '+macName+' rigid body macro file',
1536            defaultDir=defDir,defaultFile="",wildcard="GSAS-II macro file (*.mac)|*.mac",
1537            style=wx.FD_OPEN | wx.FD_CHANGE_DIR)
1538        try:
1539            if dlg.ShowModal() == wx.ID_OK:
1540                macfile = dlg.GetPath()
1541                macro = open(macfile,'Ur')
1542                head = macro.readline()
1543                if macName not in head:
1544                    print (head)
1545                    print ('**** ERROR - wrong restraint macro file selected, try again ****')
1546                    macro = []
1547            else: # cancel was pressed
1548                macro = []
1549        finally:
1550            dlg.Destroy()
1551        return macro        #advanced past 1st line
1552       
1553    def getTextFile():
1554        dlg = wx.FileDialog(G2frame,'Choose rigid body text file', '.', '',
1555            "GSAS-II text file (*.txt)|*.txt|XYZ file (*.xyz)|*.xyz|"
1556            "Sybyl mol2 file (*.mol2)|*.mol2|PDB file (*.pdb;*.ent)|*.pdb;*.ent",
1557            wx.FD_OPEN | wx.FD_CHANGE_DIR)
1558        try:
1559            if dlg.ShowModal() == wx.ID_OK:
1560                txtfile = dlg.GetPath()
1561                ext = os.path.splitext(txtfile)[1]
1562                text = open(txtfile,'Ur')
1563            else: # cancel was pressed
1564                ext = ''
1565                text = []
1566        finally:
1567            dlg.Destroy()
1568        if 'ent' in ext:
1569            ext = '.pdb'
1570        return text,ext.lower()
1571       
1572    def OnImportRigidBody(event):
1573        page = G2frame.rbBook.GetSelection()
1574        if 'Vector' in G2frame.rbBook.GetPageText(page):
1575            pass
1576        elif 'Residue' in G2frame.rbBook.GetPageText(page):
1577            ImportResidueRB()
1578           
1579    def AddVectorRB(event):
1580        AtInfo = data['Vector']['AtInfo']
1581        dlg = G2G.MultiIntegerDialog(G2frame,'New Rigid Body',['No. atoms','No. translations'],[1,1])
1582        if dlg.ShowModal() == wx.ID_OK:
1583            nAtoms,nTrans = dlg.GetValues()
1584            rbId = ran.randint(0,sys.maxsize)
1585            vecMag = [1.0 for i in range(nTrans)]
1586            vecRef = [False for i in range(nTrans)]
1587            vecVal = [np.zeros((nAtoms,3)) for j in range(nTrans)]
1588            rbTypes = ['C' for i in range(nAtoms)]
1589            Info = G2elem.GetAtomInfo('C')
1590            AtInfo['C'] = [Info['Drad'],Info['Color']]
1591            data['Vector'][rbId] = {'RBname':'UNKRB','VectMag':vecMag,'rbXYZ':np.zeros((nAtoms,3)),
1592                'rbRef':[0,1,2,False],'VectRef':vecRef,'rbTypes':rbTypes,'rbVect':vecVal,'useCount':0}
1593            data['RBIds']['Vector'].append(rbId)
1594        dlg.Destroy()
1595        UpdateVectorRB()
1596       
1597    def AddResidueRB(event):
1598        AtInfo = data['Residue']['AtInfo']
1599        macro = getMacroFile('rigid body')
1600        if not macro:
1601            return
1602        macStr = macro.readline()
1603        while macStr:
1604            items = macStr.split()
1605            if 'I' == items[0]:
1606                rbId = ran.randint(0,sys.maxsize)
1607                rbName = items[1]
1608                rbTypes = []
1609                rbXYZ = []
1610                rbSeq = []
1611                atNames = []
1612                nAtms,nSeq,nOrig,mRef,nRef = [int(items[i]) for i in [2,3,4,5,6]]
1613                for iAtm in range(nAtms):
1614                    macStr = macro.readline().split()
1615                    atName = macStr[0]
1616                    atType = macStr[1]
1617                    atNames.append(atName)
1618                    rbXYZ.append([float(macStr[i]) for i in [2,3,4]])
1619                    rbTypes.append(atType)
1620                    if atType not in AtInfo:
1621                        Info = G2elem.GetAtomInfo(atType)
1622                        AtInfo[atType] = [Info['Drad'],Info['Color']]
1623                rbXYZ = np.array(rbXYZ)-np.array(rbXYZ[nOrig-1])
1624                for iSeq in range(nSeq):
1625                    macStr = macro.readline().split()
1626                    mSeq = int(macStr[0])
1627                    for jSeq in range(mSeq):
1628                        macStr = macro.readline().split()
1629                        iBeg = int(macStr[0])-1
1630                        iFin = int(macStr[1])-1
1631                        angle = 0.0
1632                        nMove = int(macStr[2])
1633                        iMove = [int(macStr[i])-1 for i in range(3,nMove+3)]
1634                        rbSeq.append([iBeg,iFin,angle,iMove])
1635                data['Residue'][rbId] = {'RBname':rbName,'rbXYZ':rbXYZ,'rbTypes':rbTypes,
1636                    'atNames':atNames,'rbRef':[nOrig-1,mRef-1,nRef-1,True],'rbSeq':rbSeq,
1637                    'SelSeq':[0,0],'useCount':0}
1638                data['RBIds']['Residue'].append(rbId)
1639                print ('Rigid body '+rbName+' added')
1640            macStr = macro.readline()
1641        macro.close()
1642        UpdateResidueRB()
1643       
1644    def ImportResidueRB():
1645        AtInfo = data['Residue']['AtInfo']
1646        text,ext = getTextFile()
1647        if not text:
1648            return
1649        rbId = ran.randint(0,sys.maxsize)
1650        rbTypes = []
1651        rbXYZ = []
1652        atNames = []
1653        txtStr = text.readline()
1654        if 'xyz' in ext:
1655            txtStr = text.readline()
1656            txtStr = text.readline()
1657        elif 'mol2' in ext:
1658            while 'ATOM' not in txtStr:
1659                txtStr = text.readline()
1660            txtStr = text.readline()
1661        elif 'pdb' in ext:
1662            while 'ATOM' not in txtStr[:6] and 'HETATM' not in txtStr[:6]:
1663                txtStr = text.readline()
1664                #print txtStr
1665        items = txtStr.split()
1666        while len(items):
1667            if 'txt' in ext:
1668                atName = items[0]
1669                atType = items[1]
1670                rbXYZ.append([float(items[i]) for i in [2,3,4]])
1671            elif 'xyz' in ext:
1672                atType = items[0]
1673                rbXYZ.append([float(items[i]) for i in [1,2,3]])
1674                atName = atType+str(len(rbXYZ))
1675            elif 'mol2' in ext:
1676                atType = items[1]
1677                atName = items[1]+items[0]
1678                rbXYZ.append([float(items[i]) for i in [2,3,4]])
1679            elif 'pdb' in ext:
1680                atType = items[-1]
1681                atName = items[2]
1682                xyz = txtStr[30:55].split()                   
1683                rbXYZ.append([float(x) for x in xyz])
1684            atNames.append(atName)
1685            rbTypes.append(atType)
1686            if atType not in AtInfo:
1687                Info = G2elem.GetAtomInfo(atType)
1688                AtInfo[atType] = [Info['Drad'],Info['Color']]
1689            txtStr = text.readline()
1690            if 'mol2' in ext and 'BOND' in txtStr:
1691                break
1692            if 'pdb' in ext and ('ATOM' not in txtStr[:6] and 'HETATM' not in txtStr[:6]):
1693                break
1694            items = txtStr.split()
1695        if len(atNames) < 3:
1696            G2G.G2MessageBox(G2frame,'Not enough atoms in rigid body; must be 3 or more')
1697        else:
1698            rbXYZ = np.array(rbXYZ)-np.array(rbXYZ[0])
1699            Xxyz = rbXYZ[1]
1700            X = Xxyz/np.sqrt(np.sum(Xxyz**2))
1701            Yxyz = rbXYZ[2]
1702            Y = Yxyz/np.sqrt(np.sum(Yxyz**2))
1703            Mat = G2mth.getRBTransMat(X,Y)
1704            rbXYZ = np.inner(Mat,rbXYZ).T
1705            data['Residue'][rbId] = {'RBname':'UNKRB','rbXYZ':rbXYZ,'rbTypes':rbTypes,
1706                'atNames':atNames,'rbRef':[0,1,2,False],'rbSeq':[],'SelSeq':[0,0],'useCount':0}
1707            data['RBIds']['Residue'].append(rbId)
1708            print ('Rigid body UNKRB added')
1709        text.close()
1710        UpdateResidueRB(rbId)
1711       
1712    def FindNeighbors(Orig,XYZ,atTypes,atNames,AtInfo):
1713        Radii = []
1714        for Atype in atTypes:
1715            Radii.append(AtInfo[Atype][0])
1716        Radii = np.array(Radii)
1717        Neigh = []
1718        Dx = XYZ-XYZ[Orig]
1719        dist = np.sqrt(np.sum(Dx**2,axis=1))
1720        sumR = Radii[Orig]+Radii
1721        IndB = ma.nonzero(ma.masked_greater(dist-0.85*sumR,0.))
1722        for j in IndB[0]:
1723            if j != Orig and atTypes[j] != 'H':
1724                Neigh.append(atNames[j])
1725        return Neigh
1726       
1727    def FindAllNeighbors(XYZ,atTypes,atNames,AtInfo):
1728        NeighDict = {}
1729        for iat,xyz in enumerate(atNames):
1730            NeighDict[atNames[iat]] = FindNeighbors(iat,XYZ,atTypes,atNames,AtInfo)
1731        return NeighDict
1732       
1733    def FindRiding(Orig,Pivot,NeighDict):
1734        riding = [Orig,Pivot]
1735        iAdd = 1
1736        new = True
1737        while new:
1738            newAtms = NeighDict[riding[iAdd]]
1739            for At in newAtms:
1740                new = False
1741                if At not in riding:
1742                    riding.append(At)
1743                    new = True
1744            iAdd += 1
1745            if iAdd < len(riding):
1746                new = True
1747        return riding[2:]
1748                       
1749    def OnDefineTorsSeq(event):
1750        global rbId
1751        rbData = data['Residue'][rbId]
1752        if not len(rbData):
1753            return
1754        atNames = rbData['atNames']
1755        AtInfo = data['Residue']['AtInfo']
1756        atTypes = rbData['rbTypes']
1757        XYZ = rbData['rbXYZ']
1758        neighDict = FindAllNeighbors(XYZ,atTypes,atNames,AtInfo)
1759        TargList = []           
1760        dlg = wx.SingleChoiceDialog(G2frame,'Select origin atom for torsion sequence','Origin atom',rbData['atNames'])
1761        if dlg.ShowModal() == wx.ID_OK:
1762            Orig = dlg.GetSelection()
1763            TargList = neighDict[atNames[Orig]]
1764        dlg.Destroy()
1765        if not len(TargList):
1766            return
1767        dlg = wx.SingleChoiceDialog(G2frame,'Select pivot atom for torsion sequence','Pivot atom',TargList)
1768        if dlg.ShowModal() == wx.ID_OK:
1769            Piv = atNames.index(TargList[dlg.GetSelection()])
1770            riding = FindRiding(atNames[Orig],atNames[Piv],neighDict)
1771            Riding = []
1772            for atm in riding:
1773                Riding.append(atNames.index(atm))
1774            rbData['rbSeq'].append([Orig,Piv,0.0,Riding])           
1775        dlg.Destroy()
1776        UpdateResidueRB(rbId)
1777
1778    def UpdateVectorRB(Scroll=0):
1779        AtInfo = data['Vector']['AtInfo']
1780        refChoice = {}
1781        if 'DELETED' in str(G2frame.GetStatusBar()):   #seems to be no other way to do this (wx bug)
1782            if GSASIIpath.GetConfigValue('debug'):
1783                print ('DBG_wx error: Rigid Body/Status not cleanly deleted after Refine')
1784            return
1785        SetStatusLine(' You may use e.g. "c60" or "s60" for a vector entry')
1786        def rbNameSizer(rbId,rbData):
1787
1788            def OnRBName(event):
1789                event.Skip()
1790                Obj = event.GetEventObject()
1791                rbData['RBname'] = Obj.GetValue()
1792               
1793            def OnDelRB(event):
1794                Obj = event.GetEventObject()
1795                rbId = Indx[Obj.GetId()]
1796                if rbId in data['Vector']:
1797                    del data['Vector'][rbId]
1798                    data['RBIds']['Vector'].remove(rbId)
1799                    rbData['useCount'] -= 1
1800                wx.CallAfter(UpdateVectorRB)
1801               
1802            def OnPlotRB(event):
1803                Obj = event.GetEventObject()
1804                Obj.SetValue(False)
1805                G2plt.PlotRigidBody(G2frame,'Vector',AtInfo,rbData,plotDefaults)
1806           
1807            nameSizer = wx.BoxSizer(wx.HORIZONTAL)
1808            nameSizer.Add(wx.StaticText(VectorRBDisplay,-1,'Rigid body name: '),
1809                0,wx.ALIGN_CENTER_VERTICAL)
1810            RBname = wx.TextCtrl(VectorRBDisplay,-1,rbData['RBname'])
1811            Indx[RBname.GetId()] = rbId
1812            RBname.Bind(wx.EVT_TEXT_ENTER,OnRBName)
1813            RBname.Bind(wx.EVT_KILL_FOCUS,OnRBName)
1814            nameSizer.Add(RBname,0,wx.ALIGN_CENTER_VERTICAL)
1815            nameSizer.Add((5,0),)
1816            plotRB = wx.CheckBox(VectorRBDisplay,-1,'Plot?')
1817            Indx[plotRB.GetId()] = rbId
1818            plotRB.Bind(wx.EVT_CHECKBOX,OnPlotRB)
1819            nameSizer.Add(plotRB,0,wx.ALIGN_CENTER_VERTICAL)
1820            nameSizer.Add((5,0),)
1821            if not rbData['useCount']:
1822                delRB = wx.CheckBox(VectorRBDisplay,-1,'Delete?')
1823                Indx[delRB.GetId()] = rbId
1824                delRB.Bind(wx.EVT_CHECKBOX,OnDelRB)
1825                nameSizer.Add(delRB,0,wx.ALIGN_CENTER_VERTICAL)
1826            return nameSizer
1827           
1828        def rbRefAtmSizer(rbId,rbData):
1829           
1830            def OnRefSel(event):
1831                Obj = event.GetEventObject()
1832                iref = Indx[Obj.GetId()]
1833                sel = Obj.GetValue()
1834                rbData['rbRef'][iref] = atNames.index(sel)
1835                FillRefChoice(rbId,rbData)
1836           
1837            refAtmSizer = wx.BoxSizer(wx.HORIZONTAL)
1838            atNames = [name+str(i) for i,name in enumerate(rbData['rbTypes'])]
1839            rbRef = rbData.get('rbRef',[0,1,2,False])
1840            rbData['rbRef'] = rbRef
1841            if rbData['useCount']:
1842                refAtmSizer.Add(wx.StaticText(VectorRBDisplay,-1,
1843                    'Orientation reference atoms A-B-C: %s, %s, %s'%(atNames[rbRef[0]], \
1844                     atNames[rbRef[1]],atNames[rbRef[2]])),0)
1845            else:
1846                refAtmSizer.Add(wx.StaticText(VectorRBDisplay,-1,
1847                    'Orientation reference atoms A-B-C: '),0,wx.ALIGN_CENTER_VERTICAL)
1848                for i in range(3):
1849                    choices = [atNames[j] for j in refChoice[rbId][i]]
1850                    refSel = wx.ComboBox(VectorRBDisplay,-1,value='',
1851                        choices=choices,style=wx.CB_READONLY|wx.CB_DROPDOWN)
1852                    refSel.SetValue(atNames[rbRef[i]])
1853                    refSel.Bind(wx.EVT_COMBOBOX, OnRefSel)
1854                    Indx[refSel.GetId()] = i
1855                    refAtmSizer.Add(refSel,0,wx.ALIGN_CENTER_VERTICAL)
1856            return refAtmSizer
1857                       
1858        def rbVectMag(rbId,imag,rbData):
1859           
1860            def OnRBVectorMag(event):
1861                event.Skip()
1862                Obj = event.GetEventObject()
1863                rbId,imag = Indx[Obj.GetId()]
1864                try:
1865                    val = float(Obj.GetValue())
1866                    if val <= 0.:
1867                        raise ValueError
1868                    rbData['VectMag'][imag] = val
1869                except ValueError:
1870                    pass
1871                Obj.SetValue('%8.4f'%(val))
1872                wx.CallAfter(UpdateVectorRB,VectorRB.GetScrollPos(wx.VERTICAL))
1873                G2plt.PlotRigidBody(G2frame,'Vector',AtInfo,data['Vector'][rbId],plotDefaults)
1874               
1875            def OnRBVectorRef(event):
1876                Obj = event.GetEventObject()
1877                rbId,imag = Indx[Obj.GetId()]
1878                rbData['VectRef'][imag] = Obj.GetValue()
1879                       
1880            magSizer = wx.BoxSizer(wx.HORIZONTAL)
1881            magSizer.Add(wx.StaticText(VectorRBDisplay,-1,'Translation magnitude: '),
1882                0,wx.ALIGN_CENTER_VERTICAL)
1883            magValue = wx.TextCtrl(VectorRBDisplay,-1,'%8.4f'%(rbData['VectMag'][imag]))
1884            Indx[magValue.GetId()] = [rbId,imag]
1885            magValue.Bind(wx.EVT_TEXT_ENTER,OnRBVectorMag)
1886            magValue.Bind(wx.EVT_KILL_FOCUS,OnRBVectorMag)
1887            magSizer.Add(magValue,0,wx.ALIGN_CENTER_VERTICAL)
1888            magSizer.Add((5,0),)
1889            magref = wx.CheckBox(VectorRBDisplay,-1,label=' Refine?') 
1890            magref.SetValue(rbData['VectRef'][imag])
1891            magref.Bind(wx.EVT_CHECKBOX,OnRBVectorRef)
1892            Indx[magref.GetId()] = [rbId,imag]
1893            magSizer.Add(magref,0,wx.ALIGN_CENTER_VERTICAL)
1894            return magSizer
1895           
1896        def rbVectors(rbId,imag,mag,XYZ,rbData):
1897
1898            def TypeSelect(event):
1899                AtInfo = data['Vector']['AtInfo']
1900                r,c = event.GetRow(),event.GetCol()
1901                if vecGrid.GetColLabelValue(c) == 'Type':
1902                    PE = G2elemGUI.PickElement(G2frame,oneOnly=True)
1903                    if PE.ShowModal() == wx.ID_OK:
1904                        if PE.Elem != 'None':
1905                            El = PE.Elem.strip().lower().capitalize()
1906                            if El not in AtInfo:
1907                                Info = G2elem.GetAtomInfo(El)
1908                                AtInfo[El] = [Info['Drad'],Info['Color']]
1909                            rbData['rbTypes'][r] = El
1910                            vecGrid.SetCellValue(r,c,El)
1911                    PE.Destroy()
1912                wx.CallAfter(UpdateVectorRB,VectorRB.GetScrollPos(wx.VERTICAL))
1913
1914            def ChangeCell(event):
1915                r,c =  event.GetRow(),event.GetCol()
1916                if r >= 0 and (0 <= c < 3):
1917                    try:
1918                        val = float(vecGrid.GetCellValue(r,c))
1919                        rbData['rbVect'][imag][r][c] = val
1920                    except ValueError:
1921                        pass
1922                G2plt.PlotRigidBody(G2frame,'Vector',AtInfo,data['Vector'][rbId],plotDefaults)
1923                wx.CallAfter(UpdateVectorRB,VectorRB.GetScrollPos(wx.VERTICAL))
1924
1925            vecSizer = wx.BoxSizer()
1926            Types = 3*[wg.GRID_VALUE_FLOAT+':10,5',]+[wg.GRID_VALUE_STRING,]+3*[wg.GRID_VALUE_FLOAT+':10,5',]
1927            colLabels = ['Vector x','Vector y','Vector z','Type','Cart x','Cart y','Cart z']
1928            table = []
1929            rowLabels = []
1930            for ivec,xyz in enumerate(rbData['rbVect'][imag]):
1931                table.append(list(xyz)+[rbData['rbTypes'][ivec],]+list(XYZ[ivec]))
1932                rowLabels.append(str(ivec))
1933            vecTable = G2G.Table(table,rowLabels=rowLabels,colLabels=colLabels,types=Types)
1934            vecGrid = G2G.GSGrid(VectorRBDisplay)
1935            vecGrid.SetTable(vecTable, True)
1936            if 'phoenix' in wx.version():
1937                vecGrid.Bind(wg.EVT_GRID_CELL_CHANGED, ChangeCell)
1938            else:
1939                vecGrid.Bind(wg.EVT_GRID_CELL_CHANGE, ChangeCell)
1940            if not imag:
1941                vecGrid.Bind(wg.EVT_GRID_CELL_LEFT_DCLICK, TypeSelect)
1942            attr = wx.grid.GridCellAttr()
1943            attr.SetEditor(G2G.GridFractionEditor(vecGrid))
1944            for c in range(3):
1945                vecGrid.SetColAttr(c, attr)
1946            for row in range(vecTable.GetNumberRows()):
1947                if imag:
1948                    vecGrid.SetCellStyle(row,3,VERY_LIGHT_GREY,True)                   
1949                for col in [4,5,6]:
1950                    vecGrid.SetCellStyle(row,col,VERY_LIGHT_GREY,True)
1951#            vecGrid.SetScrollRate(0,0)
1952            vecGrid.AutoSizeColumns(False)
1953            vecSizer.Add(vecGrid)
1954            return vecSizer
1955       
1956        def FillRefChoice(rbId,rbData):
1957            choiceIds = [i for i in range(len(rbData['rbTypes']))]
1958           
1959            rbRef = rbData.get('rbRef',[-1,-1,-1,False])
1960            for i in range(3):
1961                choiceIds.remove(rbRef[i])
1962            refChoice[rbId] = [choiceIds[:],choiceIds[:],choiceIds[:]]
1963            for i in range(3):
1964                refChoice[rbId][i].append(rbRef[i])
1965                refChoice[rbId][i].sort()     
1966           
1967        if VectorRB.GetSizer(): VectorRB.GetSizer().Clear(True)
1968        VectorRBSizer = wx.BoxSizer(wx.VERTICAL)
1969        for rbId in data['RBIds']['Vector']:
1970            if rbId != 'AtInfo':
1971                rbData = data['Vector'][rbId]
1972                FillRefChoice(rbId,rbData)
1973                VectorRBSizer.Add(rbNameSizer(rbId,rbData),0)
1974                VectorRBSizer.Add(rbRefAtmSizer(rbId,rbData),0)
1975                XYZ = np.array([[0.,0.,0.] for Ty in rbData['rbTypes']])
1976                for imag,mag in enumerate(rbData['VectMag']):
1977                    XYZ += mag*rbData['rbVect'][imag]
1978                    VectorRBSizer.Add(rbVectMag(rbId,imag,rbData),0)
1979                    VectorRBSizer.Add(rbVectors(rbId,imag,mag,XYZ,rbData),0)
1980                VectorRBSizer.Add((5,5),0)
1981                data['Vector'][rbId]['rbXYZ'] = XYZ       
1982        VectorRBSizer.Layout()   
1983        VectorRBDisplay.SetSizer(VectorRBSizer,True)
1984        Size = VectorRBSizer.GetMinSize()
1985        Size[0] += 40
1986        Size[1] = max(Size[1],450) + 20
1987        VectorRBDisplay.SetSize(Size)
1988        VectorRB.SetScrollbars(10,10,Size[0]/10-4,Size[1]/10-1)
1989        VectorRB.Scroll(0,Scroll)
1990       
1991    def UpdateResidueRB(rbId=0):
1992        AtInfo = data['Residue']['AtInfo']
1993        refChoice = {}
1994        RefObjs = []
1995
1996        def rbNameSizer(rbId,rbData):
1997
1998            def OnRBName(event):
1999                Obj = event.GetEventObject()
2000                rbData['RBname'] = Obj.GetValue()
2001                wx.CallAfter(UpdateResidueRB,rbId)
2002               
2003            def OnDelRB(event):
2004                Obj = event.GetEventObject()
2005                rbId = Indx[Obj.GetId()]
2006                if rbId in data['Residue']: 
2007                    del data['Residue'][rbId]
2008                    data['RBIds']['Residue'].remove(rbId)
2009                wx.CallAfter(UpdateResidueRB)
2010               
2011            def OnStripH(event):
2012                Obj = event.GetEventObject()
2013                rbId = Indx[Obj.GetId()]
2014                if rbId in data['Residue']:
2015                    newNames = []
2016                    newTypes = []
2017                    newXYZ = []
2018                    for i,atype in enumerate(rbData['rbTypes']):
2019                        if atype != 'H':
2020                            newNames.append(rbData['atNames'][i])
2021                            newTypes.append(rbData['rbTypes'][i])
2022                            newXYZ.append(rbData['rbXYZ'][i])
2023                    rbData['atNames'] = newNames
2024                    rbData['rbTypes'] = newTypes
2025                    rbData['rbXYZ'] = newXYZ
2026                G2plt.PlotRigidBody(G2frame,'Residue',AtInfo,rbData,plotDefaults)
2027                wx.CallAfter(UpdateResidueRB,rbId)
2028                   
2029            def OnPlotRB(event):
2030                Obj = event.GetEventObject()
2031                Obj.SetValue(False)
2032                G2plt.PlotRigidBody(G2frame,'Residue',AtInfo,rbData,plotDefaults)
2033           
2034            nameSizer = wx.BoxSizer(wx.HORIZONTAL)
2035            nameSizer.Add(wx.StaticText(ResidueRBDisplay,-1,'Residue name: '),
2036                0,wx.ALIGN_CENTER_VERTICAL)
2037            RBname = wx.TextCtrl(ResidueRBDisplay,-1,rbData['RBname'])
2038            Indx[RBname.GetId()] = rbId
2039            RBname.Bind(wx.EVT_TEXT_ENTER,OnRBName)
2040            RBname.Bind(wx.EVT_KILL_FOCUS,OnRBName)
2041            nameSizer.Add(RBname,0,wx.ALIGN_CENTER_VERTICAL)
2042            nameSizer.Add((5,0),)
2043            plotRB = wx.CheckBox(ResidueRBDisplay,-1,'Plot?')
2044            Indx[plotRB.GetId()] = rbId
2045            plotRB.Bind(wx.EVT_CHECKBOX,OnPlotRB)
2046            nameSizer.Add(plotRB,0,wx.ALIGN_CENTER_VERTICAL)
2047            nameSizer.Add((5,0),)
2048            if not rbData['useCount']:
2049                delRB = wx.CheckBox(ResidueRBDisplay,-1,'Delete?')
2050                Indx[delRB.GetId()] = rbId
2051                delRB.Bind(wx.EVT_CHECKBOX,OnDelRB)
2052                nameSizer.Add(delRB,0,wx.ALIGN_CENTER_VERTICAL)
2053                if 'H'  in rbData['rbTypes']:
2054                    stripH = wx.CheckBox(ResidueRBDisplay,-1,'Strip H-atoms?')
2055                    Indx[stripH.GetId()] = rbId
2056                    stripH.Bind(wx.EVT_CHECKBOX,OnStripH)
2057                    nameSizer.Add(stripH,0,wx.ALIGN_CENTER_VERTICAL)
2058            return nameSizer
2059           
2060        def rbResidues(rbId,rbData):
2061           
2062            def TypeSelect(event):
2063                AtInfo = data['Residue']['AtInfo']
2064                r,c = event.GetRow(),event.GetCol()
2065                if vecGrid.GetColLabelValue(c) == 'Type':
2066                    PE = G2elemGUI.PickElement(G2frame,oneOnly=True)
2067                    if PE.ShowModal() == wx.ID_OK:
2068                        if PE.Elem != 'None':
2069                            El = PE.Elem.strip().lower().capitalize()
2070                            if El not in AtInfo:
2071                                Info = G2elem.GetAtomInfo(El)
2072                                AtInfo[El] = [Info['Drad']['Color']]
2073                            rbData['rbTypes'][r] = El
2074                            vecGrid.SetCellValue(r,c,El)
2075                    PE.Destroy()
2076
2077            def ChangeCell(event):
2078                r,c =  event.GetRow(),event.GetCol()
2079                if r >= 0 and (0 <= c < 3):
2080                    try:
2081                        val = float(vecGrid.GetCellValue(r,c))
2082                        rbData['rbXYZ'][r][c] = val
2083                    except ValueError:
2084                        pass
2085                       
2086            def RowSelect(event):
2087                r,c =  event.GetRow(),event.GetCol()
2088                if c < 0:                   #only row clicks
2089                    for vecgrid in resList:
2090                        vecgrid.ClearSelection()
2091                    vecGrid.SelectRow(r,True)
2092
2093            def OnRefSel(event):
2094               
2095                Obj = event.GetEventObject()
2096                iref,res,jref = Indx[Obj.GetId()]
2097                sel = Obj.GetValue()
2098                ind = atNames.index(sel)
2099                if rbData['rbTypes'][ind] == 'H':
2100                    G2G.G2MessageBox(G2frame,'You should not select an H-atom for rigid body orientation')
2101                rbData['rbRef'][iref] = ind
2102                FillRefChoice(rbId,rbData)
2103                for i,ref in enumerate(RefObjs[jref]):
2104                    ref.SetItems([atNames[j] for j in refChoice[rbId][i]])
2105                    ref.SetValue(atNames[rbData['rbRef'][i]])                   
2106                rbXYZ = rbData['rbXYZ']
2107                if not iref:     #origin change
2108                    rbXYZ -= rbXYZ[ind]
2109                #TODO - transform all atom XYZ by axis choices
2110                Xxyz = rbXYZ[rbData['rbRef'][1]]
2111                X = Xxyz/np.sqrt(np.sum(Xxyz**2))
2112                Yxyz = rbXYZ[rbData['rbRef'][2]]
2113                Y = Yxyz/np.sqrt(np.sum(Yxyz**2))
2114                Mat = G2mth.getRBTransMat(X,Y)
2115                rbXYZ = np.inner(Mat,rbXYZ).T
2116                rbData['rbXYZ'] = rbXYZ
2117                res.ClearSelection()
2118                resTable = res.GetTable()
2119                for r in range(res.GetNumberRows()):
2120                    row = resTable.GetRowValues(r)
2121                    row[2:4] = rbXYZ[r]
2122                    resTable.SetRowValues(r,row)
2123                res.ForceRefresh()
2124                G2plt.PlotRigidBody(G2frame,'Residue',AtInfo,rbData,plotDefaults)
2125               
2126            Types = 2*[wg.GRID_VALUE_STRING,]+3*[wg.GRID_VALUE_FLOAT+':10,5',]
2127            colLabels = ['Name','Type','Cart x','Cart y','Cart z']
2128            table = []
2129            rowLabels = []
2130            for ivec,xyz in enumerate(rbData['rbXYZ']):
2131                table.append([rbData['atNames'][ivec],]+[rbData['rbTypes'][ivec],]+list(xyz))
2132                rowLabels.append(str(ivec))
2133            vecTable = G2G.Table(table,rowLabels=rowLabels,colLabels=colLabels,types=Types)
2134            vecGrid = G2G.GSGrid(ResidueRBDisplay)
2135            Indx[vecGrid.GetId()] = rbId
2136            resList.append(vecGrid)
2137            vecGrid.SetTable(vecTable, True)
2138            if 'phoenix' in wx.version():
2139                vecGrid.Bind(wg.EVT_GRID_CELL_CHANGED, ChangeCell)
2140            else:
2141                vecGrid.Bind(wg.EVT_GRID_CELL_CHANGE, ChangeCell)
2142            vecGrid.Bind(wg.EVT_GRID_CELL_LEFT_DCLICK, TypeSelect)
2143            vecGrid.Bind(wg.EVT_GRID_LABEL_LEFT_CLICK, RowSelect)
2144            attr = wx.grid.GridCellAttr()
2145            attr.SetEditor(G2G.GridFractionEditor(vecGrid))
2146            for c in range(3):
2147                vecGrid.SetColAttr(c, attr)
2148            for row in range(vecTable.GetNumberRows()):
2149                for col in range(5):
2150                    vecGrid.SetCellStyle(row,col,VERY_LIGHT_GREY,True)
2151            vecGrid.AutoSizeColumns(False)
2152            vecSizer = wx.BoxSizer()
2153            vecSizer.Add(vecGrid)
2154           
2155            refAtmSizer = wx.BoxSizer(wx.HORIZONTAL)
2156            atNames = rbData['atNames']
2157            rbRef = rbData['rbRef']
2158            if rbData['rbRef'][3] or rbData['useCount']:
2159                refAtmSizer.Add(wx.StaticText(ResidueRBDisplay,-1,
2160                    'Orientation reference non-H atoms A-B-C: %s, %s, %s'%(atNames[rbRef[0]], \
2161                     atNames[rbRef[1]],atNames[rbRef[2]])),0)
2162            else:
2163                refAtmSizer.Add(wx.StaticText(ResidueRBDisplay,-1,
2164                    'Orientation reference non-H atoms A-B-C: '),0,wx.ALIGN_CENTER_VERTICAL)
2165                refObj = [0,0,0]
2166                for i in range(3):
2167                    choices = [atNames[j] for j in refChoice[rbId][i]]
2168                    refSel = wx.ComboBox(ResidueRBDisplay,-1,value='',
2169                        choices=choices,style=wx.CB_READONLY|wx.CB_DROPDOWN)
2170                    refSel.SetValue(atNames[rbRef[i]])
2171                    refSel.Bind(wx.EVT_COMBOBOX, OnRefSel)
2172                    Indx[refSel.GetId()] = [i,vecGrid,len(RefObjs)]
2173                    refObj[i] = refSel
2174                    refAtmSizer.Add(refSel,0,wx.ALIGN_CENTER_VERTICAL)
2175                RefObjs.append(refObj)
2176           
2177            mainSizer = wx.BoxSizer(wx.VERTICAL)
2178            mainSizer.Add(refAtmSizer)
2179            mainSizer.Add(vecSizer)
2180            return mainSizer
2181           
2182        def SeqSizer(angSlide,rbId,iSeq,Seq,atNames):
2183           
2184            def ChangeAngle(event):
2185                event.Skip()
2186                Obj = event.GetEventObject()
2187                rbId,Seq = Indx[Obj.GetId()][:2]
2188                val = Seq[2]
2189                try:
2190                    val = float(Obj.GetValue())
2191                    Seq[2] = val
2192                except ValueError:
2193                    pass
2194                Obj.SetValue('%8.2f'%(val))
2195                G2plt.PlotRigidBody(G2frame,'Residue',AtInfo,data['Residue'][rbId],plotDefaults)
2196               
2197            def OnRadBtn(event):
2198                Obj = event.GetEventObject()
2199                Seq,iSeq,angId = Indx[Obj.GetId()]
2200                data['Residue'][rbId]['SelSeq'] = [iSeq,angId]
2201                angSlide.SetValue(int(100*Seq[2]))
2202               
2203            def OnDelBtn(event):
2204                Obj = event.GetEventObject()
2205                rbId,Seq = Indx[Obj.GetId()]
2206                data['Residue'][rbId]['rbSeq'].remove(Seq)       
2207                wx.CallAfter(UpdateResidueRB,rbId)
2208           
2209            seqSizer = wx.FlexGridSizer(0,5,2,2)
2210            seqSizer.AddGrowableCol(3,0)
2211            iBeg,iFin,angle,iMove = Seq
2212            ang = wx.TextCtrl(ResidueRBDisplay,-1,'%8.2f'%(angle),size=(50,20))
2213            if not iSeq:
2214                radBt = wx.RadioButton(ResidueRBDisplay,-1,'',style=wx.RB_GROUP)
2215                data['Residue'][rbId]['SelSeq'] = [iSeq,ang.GetId()]
2216            else:
2217                radBt = wx.RadioButton(ResidueRBDisplay,-1,'')
2218            radBt.Bind(wx.EVT_RADIOBUTTON,OnRadBtn)                   
2219            seqSizer.Add(radBt)
2220            delBt = wx.RadioButton(ResidueRBDisplay,-1,'')
2221            delBt.Bind(wx.EVT_RADIOBUTTON,OnDelBtn)
2222            seqSizer.Add(delBt)
2223            bond = wx.TextCtrl(ResidueRBDisplay,-1,'%s %s'%(atNames[iBeg],atNames[iFin]),size=(50,20))
2224            seqSizer.Add(bond,0,wx.ALIGN_CENTER_VERTICAL)
2225            Indx[radBt.GetId()] = [Seq,iSeq,ang.GetId()]
2226            Indx[delBt.GetId()] = [rbId,Seq]
2227            Indx[ang.GetId()] = [rbId,Seq,ang]
2228            ang.Bind(wx.EVT_TEXT_ENTER,ChangeAngle)
2229            ang.Bind(wx.EVT_KILL_FOCUS,ChangeAngle)
2230            seqSizer.Add(ang,0,wx.ALIGN_CENTER_VERTICAL)
2231            atms = ''
2232            for i in iMove:   
2233                atms += ' %s,'%(atNames[i])
2234            moves = wx.TextCtrl(ResidueRBDisplay,-1,atms[:-1],size=(200,20))
2235            seqSizer.Add(moves,1,wx.ALIGN_CENTER_VERTICAL|wx.EXPAND|wx.RIGHT)
2236            return seqSizer
2237           
2238        def SlideSizer():
2239           
2240            def OnSlider(event):
2241                Obj = event.GetEventObject()
2242                rbData = Indx[Obj.GetId()]
2243                iSeq,angId = rbData['SelSeq']
2244                val = float(Obj.GetValue())/100.
2245                rbData['rbSeq'][iSeq][2] = val
2246                Indx[angId][2].SetValue('%8.2f'%(val))
2247                G2plt.PlotRigidBody(G2frame,'Residue',AtInfo,rbData,plotDefaults)
2248           
2249            slideSizer = wx.BoxSizer(wx.HORIZONTAL)
2250            slideSizer.Add(wx.StaticText(ResidueRBDisplay,-1,'Selected torsion angle:'),0)
2251            iSeq,angId = rbData['SelSeq']
2252            angSlide = wx.Slider(ResidueRBDisplay,-1,
2253                int(100*rbData['rbSeq'][iSeq][2]),0,36000,size=(200,20),
2254                style=wx.SL_HORIZONTAL)
2255            angSlide.Bind(wx.EVT_SLIDER, OnSlider)
2256            Indx[angSlide.GetId()] = rbData
2257            slideSizer.Add(angSlide,0)           
2258            return slideSizer,angSlide
2259           
2260        def FillRefChoice(rbId,rbData):
2261            choiceIds = [i for i in range(len(rbData['atNames']))]
2262            for seq in rbData['rbSeq']:
2263                for i in seq[3]:
2264                    try:
2265                        choiceIds.remove(i)
2266                    except ValueError:
2267                        pass
2268            rbRef = rbData['rbRef']
2269            for i in range(3):
2270                try:
2271                    choiceIds.remove(rbRef[i])
2272                except ValueError:
2273                    pass
2274            refChoice[rbId] = [choiceIds[:],choiceIds[:],choiceIds[:]]
2275            for i in range(3):
2276                refChoice[rbId][i].append(rbRef[i])
2277                refChoice[rbId][i].sort()
2278               
2279        def OnSelect(event):
2280            rbname = rbchoice[select.GetSelection()]
2281            rbId = RBnames[rbname]
2282            wx.CallLater(100,UpdateResidueRB,rbId)
2283           
2284        GS = ResidueRBDisplay.GetSizer()
2285        if GS: 
2286            try:        #get around a c++ error in wx 4.0; doing is again seems to be OK
2287                GS.Clear(True)
2288            except:
2289                GS.Clear(True)
2290       
2291        RBnames = {}
2292        for rbid in data['RBIds']['Residue']:
2293            RBnames.update({data['Residue'][rbid]['RBname']:rbid,})
2294        if not RBnames:
2295            return
2296        rbchoice = list(RBnames.keys())
2297        ResidueRBSizer = wx.BoxSizer(wx.VERTICAL)
2298        if len(RBnames) > 1:
2299            selSizer = wx.BoxSizer(wx.HORIZONTAL)
2300            selSizer.Add(wx.StaticText(ResidueRBDisplay,label=' Select residue to view:'),0)
2301            rbchoice.sort()
2302            select = wx.ComboBox(ResidueRBDisplay,choices=rbchoice)
2303            select.Bind(wx.EVT_COMBOBOX,OnSelect)
2304            selSizer.Add(select,0)
2305            ResidueRBSizer.Add(selSizer,0)
2306        if not rbId:
2307            rbId = RBnames[rbchoice[0]]
2308        rbData = data['Residue'][rbId]
2309        FillRefChoice(rbId,rbData)
2310        ResidueRBSizer.Add(rbNameSizer(rbId,rbData),0)
2311        ResidueRBSizer.Add(rbResidues(rbId,rbData),0)
2312        ResidueRBSizer.Add((5,5),0)
2313        if rbData['rbSeq']:
2314            slideSizer,angSlide = SlideSizer()
2315        if len(rbData['rbSeq']):
2316            ResidueRBSizer.Add(wx.StaticText(ResidueRBDisplay,-1,
2317                'Sel  Del  Bond             Angle      Riding atoms'),
2318                0,wx.ALIGN_CENTER_VERTICAL)                       
2319        for iSeq,Seq in enumerate(rbData['rbSeq']):
2320            ResidueRBSizer.Add(SeqSizer(angSlide,rbId,iSeq,Seq,rbData['atNames']))
2321        if rbData['rbSeq']:
2322            ResidueRBSizer.Add(slideSizer,)
2323
2324        ResidueRBSizer.Add((5,25),)
2325        ResidueRBSizer.Layout()   
2326        ResidueRBDisplay.SetSizer(ResidueRBSizer,True)
2327        ResidueRBDisplay.SetAutoLayout(True)
2328        Size = ResidueRBSizer.GetMinSize()
2329        ResidueRBDisplay.SetSize(Size)
2330        ResidueRBDisplay.Show()
2331       
2332    def SetStatusLine(text):
2333        G2frame.GetStatusBar().SetStatusText(text,1)                                     
2334
2335    G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.RigidBodyMenu)
2336    SetStatusLine('')
2337    UpdateVectorRB()
2338    G2frame.rbBook.Bind(wx.aui.EVT_AUINOTEBOOK_PAGE_CHANGED, OnPageChanged)
2339    wx.CallAfter(OnPageChanged,None)
Note: See TracBrowser for help on using the repository browser.