source: trunk/GSASIIconstrGUI.py @ 3409

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

another fix to transformation constraints to skip equivalent positions/thermal parms constrained by site symmetry

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