source: trunk/GSASIIconstrGUI.py @ 3699

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

make check for duplicate constraints (via strings) in TransConstraints? - skip if already present

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