source: trunk/GSASIIconstrGUI.py @ 3691

Last change on this file since 3691 was 3691, checked in by vondreele, 3 years ago

fix constraints on transformed lattice parameters to preserve higher symmetry in lower symm subgroups

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