source: trunk/GSASIIconstrGUI.py @ 3577

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

delete MAXMAGN - not using it any more
fix (maybe) mag constraints; works for orthogonal axes
fix bug in ReadPowerInstprm?
spelling of Levenberg!
fixes to mag structure from Bilbao stuff
set dmin to be min 1A for UnitCell? show reflections
fix Latt2text to show '-' & Trans2text
fix ext test in GetHistogramPhaseData? for mag structures

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