source: trunk/GSASIIconstrGUI.py @ 3703

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

refactoring of GetSSfxuinel

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Author Revision URL Id
File size: 99.1 KB
Line 
1# -*- coding: utf-8 -*-
2#GSASIIconstrGUI - constraint GUI routines
3########### SVN repository information ###################
4# $Date: 2018-10-24 21:30:34 +0000 (Wed, 24 Oct 2018) $
5# $Author: vondreele $
6# $Revision: 3703 $
7# $URL: trunk/GSASIIconstrGUI.py $
8# $Id: GSASIIconstrGUI.py 3703 2018-10-24 21:30:34Z 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: 3703 $")
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    nAcof = G2lat.cell2A(newPhase['General']['Cell'][1:7])
1290   
1291    opId = oldPhase['pId']
1292    npId = newPhase['pId']
1293    cx,ct,cs,cia = newPhase['General']['AtomPtrs']
1294    nAtoms = newPhase['Atoms']
1295    oSGData = oldPhase['General']['SGData']
1296    nSGData = newPhase['General']['SGData']
1297    oAcof = G2lat.cell2A(oldPhase['General']['Cell'][1:7])
1298    nAcof = G2lat.cell2A(newPhase['General']['Cell'][1:7])
1299    item = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'Constraints')
1300    if not item:
1301        return
1302    constraints = G2frame.GPXtree.GetItemPyData(item)
1303    parmDict = {}
1304    varyList = []
1305    xnames = ['dAx','dAy','dAz']
1306#    invTrans = nl.inv(Trans)
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#        conStrings = []
1359#        for iU,Usi in enumerate(Us):
1360#            parm = '%d::%s;%d'%(npId,Usi,ia)
1361#            parmDict[parm] = nUcof[iU]
1362#            varyList.append(parm)
1363#            IndpCon = [1.0,G2obj.G2VarObj(parm)]
1364#            conStr = str([IndpCon,DepConsDict[Usi]])
1365#            if conStr in conStrings:
1366#                continue
1367#            conStrings.append(conStr)
1368#            if len(DepConsDict[Usi]) == 1:
1369#                if DepConsDict[Usi][0]:
1370#                    constraints['Phase'].append([IndpCon,DepConsDict[Usi][0],None,None,'e'])
1371#            elif len(DepConsDict[Usi]) > 1:       
1372#                for Dep in DepConsDict[Usi]:
1373#                    Dep[0] *= -1
1374#                constraints['Phase'].append([IndpCon]+DepConsDict[Usi]+[0.0,None,'c'])
1375           
1376        #how do I do Uij's for most Trans?
1377    As = ['A0','A1','A2','A3','A4','A5']
1378    Aids = [[0,0,'A0'],[1,1,'A1'],[2,2,'A2'],[0,1,'A3'],[0,2,'A4'],[1,2,'A5']]
1379    DepConsDict = dict(zip(As,[[],[],[],[],[],[]]))
1380    for iA,Aid in enumerate(Aids):
1381        GT = np.zeros((3,3))
1382        if abs(nAcof[iA]) > 1.e-8:
1383            GT[Aid[0],Aid[1]] = 1
1384            nGT = G2lat.prodMGMT(GT,Trans)
1385            nAT = G2lat.Gmat2A(nGT)
1386            for ia,nA in enumerate(nAT):
1387                if abs(nA) > 1.e-8 and abs(nAcof[ia]) > 1.e-8:
1388                    parm = SetUniqAj(npId,As[ia],nSGData['SGLaue'])
1389                    DepConsDict[Aid[2]].append([nA,G2obj.G2VarObj(parm)])
1390    conStrings = []
1391    for iA,Asi in enumerate(As):
1392        parm = SetUniqAj(opId,Asi,oSGData['SGLaue'])
1393        parmDict[parm] = oAcof[iA]
1394        varyList.append(parm)
1395        IndpCon = [1.0,G2obj.G2VarObj(parm)]
1396        conStr = str([IndpCon,DepConsDict[Asi]])
1397        if conStr in conStrings:
1398            continue
1399        conStrings.append(conStr)
1400        if len(DepConsDict[Asi]) == 1:
1401            if DepConsDict[Asi][0]:
1402                constraints['Phase'].append([IndpCon,DepConsDict[Asi][0],None,None,'e'])
1403        elif len(DepConsDict[Asi]) > 1:       
1404            for Dep in DepConsDict[Asi]:
1405                Dep[0] *= -1
1406            constraints['Phase'].append([IndpCon]+DepConsDict[Asi]+[0.0,None,'c'])
1407    for hId,hist in enumerate(UseList):    #HAP - seems OK
1408        ohapkey = '%d:%d:'%(opId,hId)
1409        nhapkey = '%d:%d:'%(npId,hId)
1410        IndpCon = [1.0,G2obj.G2VarObj(ohapkey+'Scale')]
1411        DepCons = [detTrans,G2obj.G2VarObj(nhapkey+'Scale')]
1412        constraints['HAP'].append([DepCons,IndpCon,None,None,'e'])
1413        for name in ['Size;i','Mustrain;i']:
1414            IndpCon = [1.0,G2obj.G2VarObj(ohapkey+name)]
1415            DepCons = [1.0,G2obj.G2VarObj(nhapkey+name)]
1416            constraints['HAP'].append([IndpCon,DepCons,None,None,'e'])
1417       
1418################################################################################
1419#### Rigid bodies
1420################################################################################
1421
1422def UpdateRigidBodies(G2frame,data):
1423    '''Called when Rigid bodies tree item is selected.
1424    Displays the rigid bodies in the data window
1425    '''
1426    if not data.get('RBIds') or not data:
1427        data.update({'Vector':{'AtInfo':{}},'Residue':{'AtInfo':{}},
1428            'RBIds':{'Vector':[],'Residue':[]}})       #empty/bad dict - fill it
1429           
1430    global resList,rbId
1431    Indx = {}
1432    resList = []
1433    plotDefaults = {'oldxy':[0.,0.],'Quaternion':[0.,0.,0.,1.],'cameraPos':30.,'viewDir':[0,0,1],}
1434
1435    G2frame.rbBook = G2G.GSNoteBook(parent=G2frame.dataWindow)
1436    G2frame.dataWindow.GetSizer().Add(G2frame.rbBook,1,wx.ALL|wx.EXPAND)
1437    VectorRB = wx.ScrolledWindow(G2frame.rbBook)
1438    VectorRBDisplay = wx.Panel(VectorRB)
1439    G2frame.rbBook.AddPage(VectorRB,'Vector rigid bodies')
1440    ResidueRB = wx.ScrolledWindow(G2frame.rbBook)
1441    ResidueRBDisplay = wx.Panel(ResidueRB)
1442    G2frame.rbBook.AddPage(ResidueRB,'Residue rigid bodies')
1443   
1444    def OnPageChanged(event):
1445        global resList
1446        resList = []
1447        if event:       #page change event!
1448            page = event.GetSelection()
1449        else:
1450            page = G2frame.rbBook.GetSelection()
1451        G2frame.rbBook.ChangeSelection(page)
1452        text = G2frame.rbBook.GetPageText(page)
1453        if text == 'Vector rigid bodies':
1454            G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.VectorBodyMenu)
1455            G2frame.Bind(wx.EVT_MENU, AddVectorRB, id=G2G.wxID_VECTORBODYADD)
1456            G2frame.Page = [page,'vrb']
1457            UpdateVectorRB()
1458        elif text == 'Residue rigid bodies':
1459            G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.RigidBodyMenu)
1460            G2frame.Bind(wx.EVT_MENU, AddResidueRB, id=G2G.wxID_RIGIDBODYADD)
1461            G2frame.Bind(wx.EVT_MENU, OnImportRigidBody, id=G2G.wxID_RIGIDBODYIMPORT)
1462            G2frame.Bind(wx.EVT_MENU, OnDefineTorsSeq, id=G2G.wxID_RESIDUETORSSEQ) #enable only if residue RBs exist?
1463            G2frame.Page = [page,'rrb']
1464            UpdateResidueRB()
1465           
1466    def getMacroFile(macName):
1467        defDir = os.path.join(os.path.split(__file__)[0],'GSASIImacros')
1468        dlg = wx.FileDialog(G2frame,message='Choose '+macName+' rigid body macro file',
1469            defaultDir=defDir,defaultFile="",wildcard="GSAS-II macro file (*.mac)|*.mac",
1470            style=wx.FD_OPEN | wx.FD_CHANGE_DIR)
1471        try:
1472            if dlg.ShowModal() == wx.ID_OK:
1473                macfile = dlg.GetPath()
1474                macro = open(macfile,'Ur')
1475                head = macro.readline()
1476                if macName not in head:
1477                    print (head)
1478                    print ('**** ERROR - wrong restraint macro file selected, try again ****')
1479                    macro = []
1480            else: # cancel was pressed
1481                macro = []
1482        finally:
1483            dlg.Destroy()
1484        return macro        #advanced past 1st line
1485       
1486    def getTextFile():
1487        dlg = wx.FileDialog(G2frame,'Choose rigid body text file', '.', '',
1488            "GSAS-II text file (*.txt)|*.txt|XYZ file (*.xyz)|*.xyz|"
1489            "Sybyl mol2 file (*.mol2)|*.mol2|PDB file (*.pdb;*.ent)|*.pdb;*.ent",
1490            wx.FD_OPEN | wx.FD_CHANGE_DIR)
1491        try:
1492            if dlg.ShowModal() == wx.ID_OK:
1493                txtfile = dlg.GetPath()
1494                ext = os.path.splitext(txtfile)[1]
1495                text = open(txtfile,'Ur')
1496            else: # cancel was pressed
1497                ext = ''
1498                text = []
1499        finally:
1500            dlg.Destroy()
1501        if 'ent' in ext:
1502            ext = '.pdb'
1503        return text,ext.lower()
1504       
1505    def OnImportRigidBody(event):
1506        page = G2frame.rbBook.GetSelection()
1507        if 'Vector' in G2frame.rbBook.GetPageText(page):
1508            pass
1509        elif 'Residue' in G2frame.rbBook.GetPageText(page):
1510            ImportResidueRB()
1511           
1512    def AddVectorRB(event):
1513        AtInfo = data['Vector']['AtInfo']
1514        dlg = G2G.MultiIntegerDialog(G2frame,'New Rigid Body',['No. atoms','No. translations'],[1,1])
1515        if dlg.ShowModal() == wx.ID_OK:
1516            nAtoms,nTrans = dlg.GetValues()
1517            rbId = ran.randint(0,sys.maxsize)
1518            vecMag = [1.0 for i in range(nTrans)]
1519            vecRef = [False for i in range(nTrans)]
1520            vecVal = [np.zeros((nAtoms,3)) for j in range(nTrans)]
1521            rbTypes = ['C' for i in range(nAtoms)]
1522            Info = G2elem.GetAtomInfo('C')
1523            AtInfo['C'] = [Info['Drad'],Info['Color']]
1524            data['Vector'][rbId] = {'RBname':'UNKRB','VectMag':vecMag,'rbXYZ':np.zeros((nAtoms,3)),
1525                'rbRef':[0,1,2,False],'VectRef':vecRef,'rbTypes':rbTypes,'rbVect':vecVal,'useCount':0}
1526            data['RBIds']['Vector'].append(rbId)
1527        dlg.Destroy()
1528        UpdateVectorRB()
1529       
1530    def AddResidueRB(event):
1531        AtInfo = data['Residue']['AtInfo']
1532        macro = getMacroFile('rigid body')
1533        if not macro:
1534            return
1535        macStr = macro.readline()
1536        while macStr:
1537            items = macStr.split()
1538            if 'I' == items[0]:
1539                rbId = ran.randint(0,sys.maxsize)
1540                rbName = items[1]
1541                rbTypes = []
1542                rbXYZ = []
1543                rbSeq = []
1544                atNames = []
1545                nAtms,nSeq,nOrig,mRef,nRef = [int(items[i]) for i in [2,3,4,5,6]]
1546                for iAtm in range(nAtms):
1547                    macStr = macro.readline().split()
1548                    atName = macStr[0]
1549                    atType = macStr[1]
1550                    atNames.append(atName)
1551                    rbXYZ.append([float(macStr[i]) for i in [2,3,4]])
1552                    rbTypes.append(atType)
1553                    if atType not in AtInfo:
1554                        Info = G2elem.GetAtomInfo(atType)
1555                        AtInfo[atType] = [Info['Drad'],Info['Color']]
1556                rbXYZ = np.array(rbXYZ)-np.array(rbXYZ[nOrig-1])
1557                for iSeq in range(nSeq):
1558                    macStr = macro.readline().split()
1559                    mSeq = int(macStr[0])
1560                    for jSeq in range(mSeq):
1561                        macStr = macro.readline().split()
1562                        iBeg = int(macStr[0])-1
1563                        iFin = int(macStr[1])-1
1564                        angle = 0.0
1565                        nMove = int(macStr[2])
1566                        iMove = [int(macStr[i])-1 for i in range(3,nMove+3)]
1567                        rbSeq.append([iBeg,iFin,angle,iMove])
1568                data['Residue'][rbId] = {'RBname':rbName,'rbXYZ':rbXYZ,'rbTypes':rbTypes,
1569                    'atNames':atNames,'rbRef':[nOrig-1,mRef-1,nRef-1,True],'rbSeq':rbSeq,
1570                    'SelSeq':[0,0],'useCount':0}
1571                data['RBIds']['Residue'].append(rbId)
1572                print ('Rigid body '+rbName+' added')
1573            macStr = macro.readline()
1574        macro.close()
1575        UpdateResidueRB()
1576       
1577    def ImportResidueRB():
1578        AtInfo = data['Residue']['AtInfo']
1579        text,ext = getTextFile()
1580        if not text:
1581            return
1582        rbId = ran.randint(0,sys.maxsize)
1583        rbTypes = []
1584        rbXYZ = []
1585        atNames = []
1586        txtStr = text.readline()
1587        if 'xyz' in ext:
1588            txtStr = text.readline()
1589            txtStr = text.readline()
1590        elif 'mol2' in ext:
1591            while 'ATOM' not in txtStr:
1592                txtStr = text.readline()
1593            txtStr = text.readline()
1594        elif 'pdb' in ext:
1595            while 'ATOM' not in txtStr[:6] and 'HETATM' not in txtStr[:6]:
1596                txtStr = text.readline()
1597                #print txtStr
1598        items = txtStr.split()
1599        while len(items):
1600            if 'txt' in ext:
1601                atName = items[0]
1602                atType = items[1]
1603                rbXYZ.append([float(items[i]) for i in [2,3,4]])
1604            elif 'xyz' in ext:
1605                atType = items[0]
1606                rbXYZ.append([float(items[i]) for i in [1,2,3]])
1607                atName = atType+str(len(rbXYZ))
1608            elif 'mol2' in ext:
1609                atType = items[1]
1610                atName = items[1]+items[0]
1611                rbXYZ.append([float(items[i]) for i in [2,3,4]])
1612            elif 'pdb' in ext:
1613                atType = items[-1]
1614                atName = items[2]
1615                xyz = txtStr[30:55].split()                   
1616                rbXYZ.append([float(x) for x in xyz])
1617            atNames.append(atName)
1618            rbTypes.append(atType)
1619            if atType not in AtInfo:
1620                Info = G2elem.GetAtomInfo(atType)
1621                AtInfo[atType] = [Info['Drad'],Info['Color']]
1622            txtStr = text.readline()
1623            if 'mol2' in ext and 'BOND' in txtStr:
1624                break
1625            if 'pdb' in ext and ('ATOM' not in txtStr[:6] and 'HETATM' not in txtStr[:6]):
1626                break
1627            items = txtStr.split()
1628        if len(atNames) < 3:
1629            G2G.G2MessageBox(G2frame,'Not enough atoms in rigid body; must be 3 or more')
1630        else:
1631            rbXYZ = np.array(rbXYZ)-np.array(rbXYZ[0])
1632            Xxyz = rbXYZ[1]
1633            X = Xxyz/np.sqrt(np.sum(Xxyz**2))
1634            Yxyz = rbXYZ[2]
1635            Y = Yxyz/np.sqrt(np.sum(Yxyz**2))
1636            Mat = G2mth.getRBTransMat(X,Y)
1637            rbXYZ = np.inner(Mat,rbXYZ).T
1638            data['Residue'][rbId] = {'RBname':'UNKRB','rbXYZ':rbXYZ,'rbTypes':rbTypes,
1639                'atNames':atNames,'rbRef':[0,1,2,False],'rbSeq':[],'SelSeq':[0,0],'useCount':0}
1640            data['RBIds']['Residue'].append(rbId)
1641            print ('Rigid body UNKRB added')
1642        text.close()
1643        UpdateResidueRB(rbId)
1644       
1645    def FindNeighbors(Orig,XYZ,atTypes,atNames,AtInfo):
1646        Radii = []
1647        for Atype in atTypes:
1648            Radii.append(AtInfo[Atype][0])
1649        Radii = np.array(Radii)
1650        Neigh = []
1651        Dx = XYZ-XYZ[Orig]
1652        dist = np.sqrt(np.sum(Dx**2,axis=1))
1653        sumR = Radii[Orig]+Radii
1654        IndB = ma.nonzero(ma.masked_greater(dist-0.85*sumR,0.))
1655        for j in IndB[0]:
1656            if j != Orig and atTypes[j] != 'H':
1657                Neigh.append(atNames[j])
1658        return Neigh
1659       
1660    def FindAllNeighbors(XYZ,atTypes,atNames,AtInfo):
1661        NeighDict = {}
1662        for iat,xyz in enumerate(atNames):
1663            NeighDict[atNames[iat]] = FindNeighbors(iat,XYZ,atTypes,atNames,AtInfo)
1664        return NeighDict
1665       
1666    def FindRiding(Orig,Pivot,NeighDict):
1667        riding = [Orig,Pivot]
1668        iAdd = 1
1669        new = True
1670        while new:
1671            newAtms = NeighDict[riding[iAdd]]
1672            for At in newAtms:
1673                new = False
1674                if At not in riding:
1675                    riding.append(At)
1676                    new = True
1677            iAdd += 1
1678            if iAdd < len(riding):
1679                new = True
1680        return riding[2:]
1681                       
1682    def OnDefineTorsSeq(event):
1683        global rbId
1684        rbData = data['Residue'][rbId]
1685        if not len(rbData):
1686            return
1687        atNames = rbData['atNames']
1688        AtInfo = data['Residue']['AtInfo']
1689        atTypes = rbData['rbTypes']
1690        XYZ = rbData['rbXYZ']
1691        neighDict = FindAllNeighbors(XYZ,atTypes,atNames,AtInfo)
1692        TargList = []           
1693        dlg = wx.SingleChoiceDialog(G2frame,'Select origin atom for torsion sequence','Origin atom',rbData['atNames'])
1694        if dlg.ShowModal() == wx.ID_OK:
1695            Orig = dlg.GetSelection()
1696            TargList = neighDict[atNames[Orig]]
1697        dlg.Destroy()
1698        if not len(TargList):
1699            return
1700        dlg = wx.SingleChoiceDialog(G2frame,'Select pivot atom for torsion sequence','Pivot atom',TargList)
1701        if dlg.ShowModal() == wx.ID_OK:
1702            Piv = atNames.index(TargList[dlg.GetSelection()])
1703            riding = FindRiding(atNames[Orig],atNames[Piv],neighDict)
1704            Riding = []
1705            for atm in riding:
1706                Riding.append(atNames.index(atm))
1707            rbData['rbSeq'].append([Orig,Piv,0.0,Riding])           
1708        dlg.Destroy()
1709        UpdateResidueRB(rbId)
1710
1711    def UpdateVectorRB(Scroll=0):
1712        AtInfo = data['Vector']['AtInfo']
1713        refChoice = {}
1714        if 'DELETED' in str(G2frame.GetStatusBar()):   #seems to be no other way to do this (wx bug)
1715            if GSASIIpath.GetConfigValue('debug'):
1716                print ('DBG_wx error: Rigid Body/Status not cleanly deleted after Refine')
1717            return
1718        SetStatusLine(' You may use e.g. "c60" or "s60" for a vector entry')
1719        def rbNameSizer(rbId,rbData):
1720
1721            def OnRBName(event):
1722                event.Skip()
1723                Obj = event.GetEventObject()
1724                rbData['RBname'] = Obj.GetValue()
1725               
1726            def OnDelRB(event):
1727                Obj = event.GetEventObject()
1728                rbId = Indx[Obj.GetId()]
1729                if rbId in data['Vector']:
1730                    del data['Vector'][rbId]
1731                    data['RBIds']['Vector'].remove(rbId)
1732                    rbData['useCount'] -= 1
1733                wx.CallAfter(UpdateVectorRB)
1734               
1735            def OnPlotRB(event):
1736                Obj = event.GetEventObject()
1737                Obj.SetValue(False)
1738                G2plt.PlotRigidBody(G2frame,'Vector',AtInfo,rbData,plotDefaults)
1739           
1740            nameSizer = wx.BoxSizer(wx.HORIZONTAL)
1741            nameSizer.Add(wx.StaticText(VectorRBDisplay,-1,'Rigid body name: '),
1742                0,wx.ALIGN_CENTER_VERTICAL)
1743            RBname = wx.TextCtrl(VectorRBDisplay,-1,rbData['RBname'])
1744            Indx[RBname.GetId()] = rbId
1745            RBname.Bind(wx.EVT_TEXT_ENTER,OnRBName)
1746            RBname.Bind(wx.EVT_KILL_FOCUS,OnRBName)
1747            nameSizer.Add(RBname,0,wx.ALIGN_CENTER_VERTICAL)
1748            nameSizer.Add((5,0),)
1749            plotRB = wx.CheckBox(VectorRBDisplay,-1,'Plot?')
1750            Indx[plotRB.GetId()] = rbId
1751            plotRB.Bind(wx.EVT_CHECKBOX,OnPlotRB)
1752            nameSizer.Add(plotRB,0,wx.ALIGN_CENTER_VERTICAL)
1753            nameSizer.Add((5,0),)
1754            if not rbData['useCount']:
1755                delRB = wx.CheckBox(VectorRBDisplay,-1,'Delete?')
1756                Indx[delRB.GetId()] = rbId
1757                delRB.Bind(wx.EVT_CHECKBOX,OnDelRB)
1758                nameSizer.Add(delRB,0,wx.ALIGN_CENTER_VERTICAL)
1759            return nameSizer
1760           
1761        def rbRefAtmSizer(rbId,rbData):
1762           
1763            def OnRefSel(event):
1764                Obj = event.GetEventObject()
1765                iref = Indx[Obj.GetId()]
1766                sel = Obj.GetValue()
1767                rbData['rbRef'][iref] = atNames.index(sel)
1768                FillRefChoice(rbId,rbData)
1769           
1770            refAtmSizer = wx.BoxSizer(wx.HORIZONTAL)
1771            atNames = [name+str(i) for i,name in enumerate(rbData['rbTypes'])]
1772            rbRef = rbData.get('rbRef',[0,1,2,False])
1773            rbData['rbRef'] = rbRef
1774            if rbData['useCount']:
1775                refAtmSizer.Add(wx.StaticText(VectorRBDisplay,-1,
1776                    'Orientation reference atoms A-B-C: %s, %s, %s'%(atNames[rbRef[0]], \
1777                     atNames[rbRef[1]],atNames[rbRef[2]])),0)
1778            else:
1779                refAtmSizer.Add(wx.StaticText(VectorRBDisplay,-1,
1780                    'Orientation reference atoms A-B-C: '),0,wx.ALIGN_CENTER_VERTICAL)
1781                for i in range(3):
1782                    choices = [atNames[j] for j in refChoice[rbId][i]]
1783                    refSel = wx.ComboBox(VectorRBDisplay,-1,value='',
1784                        choices=choices,style=wx.CB_READONLY|wx.CB_DROPDOWN)
1785                    refSel.SetValue(atNames[rbRef[i]])
1786                    refSel.Bind(wx.EVT_COMBOBOX, OnRefSel)
1787                    Indx[refSel.GetId()] = i
1788                    refAtmSizer.Add(refSel,0,wx.ALIGN_CENTER_VERTICAL)
1789            return refAtmSizer
1790                       
1791        def rbVectMag(rbId,imag,rbData):
1792           
1793            def OnRBVectorMag(event):
1794                event.Skip()
1795                Obj = event.GetEventObject()
1796                rbId,imag = Indx[Obj.GetId()]
1797                try:
1798                    val = float(Obj.GetValue())
1799                    if val <= 0.:
1800                        raise ValueError
1801                    rbData['VectMag'][imag] = val
1802                except ValueError:
1803                    pass
1804                Obj.SetValue('%8.4f'%(val))
1805                wx.CallAfter(UpdateVectorRB,VectorRB.GetScrollPos(wx.VERTICAL))
1806                G2plt.PlotRigidBody(G2frame,'Vector',AtInfo,data['Vector'][rbId],plotDefaults)
1807               
1808            def OnRBVectorRef(event):
1809                Obj = event.GetEventObject()
1810                rbId,imag = Indx[Obj.GetId()]
1811                rbData['VectRef'][imag] = Obj.GetValue()
1812                       
1813            magSizer = wx.BoxSizer(wx.HORIZONTAL)
1814            magSizer.Add(wx.StaticText(VectorRBDisplay,-1,'Translation magnitude: '),
1815                0,wx.ALIGN_CENTER_VERTICAL)
1816            magValue = wx.TextCtrl(VectorRBDisplay,-1,'%8.4f'%(rbData['VectMag'][imag]))
1817            Indx[magValue.GetId()] = [rbId,imag]
1818            magValue.Bind(wx.EVT_TEXT_ENTER,OnRBVectorMag)
1819            magValue.Bind(wx.EVT_KILL_FOCUS,OnRBVectorMag)
1820            magSizer.Add(magValue,0,wx.ALIGN_CENTER_VERTICAL)
1821            magSizer.Add((5,0),)
1822            magref = wx.CheckBox(VectorRBDisplay,-1,label=' Refine?') 
1823            magref.SetValue(rbData['VectRef'][imag])
1824            magref.Bind(wx.EVT_CHECKBOX,OnRBVectorRef)
1825            Indx[magref.GetId()] = [rbId,imag]
1826            magSizer.Add(magref,0,wx.ALIGN_CENTER_VERTICAL)
1827            return magSizer
1828           
1829        def rbVectors(rbId,imag,mag,XYZ,rbData):
1830
1831            def TypeSelect(event):
1832                AtInfo = data['Vector']['AtInfo']
1833                r,c = event.GetRow(),event.GetCol()
1834                if vecGrid.GetColLabelValue(c) == 'Type':
1835                    PE = G2elemGUI.PickElement(G2frame,oneOnly=True)
1836                    if PE.ShowModal() == wx.ID_OK:
1837                        if PE.Elem != 'None':
1838                            El = PE.Elem.strip().lower().capitalize()
1839                            if El not in AtInfo:
1840                                Info = G2elem.GetAtomInfo(El)
1841                                AtInfo[El] = [Info['Drad'],Info['Color']]
1842                            rbData['rbTypes'][r] = El
1843                            vecGrid.SetCellValue(r,c,El)
1844                    PE.Destroy()
1845                wx.CallAfter(UpdateVectorRB,VectorRB.GetScrollPos(wx.VERTICAL))
1846
1847            def ChangeCell(event):
1848                r,c =  event.GetRow(),event.GetCol()
1849                if r >= 0 and (0 <= c < 3):
1850                    try:
1851                        val = float(vecGrid.GetCellValue(r,c))
1852                        rbData['rbVect'][imag][r][c] = val
1853                    except ValueError:
1854                        pass
1855                G2plt.PlotRigidBody(G2frame,'Vector',AtInfo,data['Vector'][rbId],plotDefaults)
1856                wx.CallAfter(UpdateVectorRB,VectorRB.GetScrollPos(wx.VERTICAL))
1857
1858            vecSizer = wx.BoxSizer()
1859            Types = 3*[wg.GRID_VALUE_FLOAT+':10,5',]+[wg.GRID_VALUE_STRING,]+3*[wg.GRID_VALUE_FLOAT+':10,5',]
1860            colLabels = ['Vector x','Vector y','Vector z','Type','Cart x','Cart y','Cart z']
1861            table = []
1862            rowLabels = []
1863            for ivec,xyz in enumerate(rbData['rbVect'][imag]):
1864                table.append(list(xyz)+[rbData['rbTypes'][ivec],]+list(XYZ[ivec]))
1865                rowLabels.append(str(ivec))
1866            vecTable = G2G.Table(table,rowLabels=rowLabels,colLabels=colLabels,types=Types)
1867            vecGrid = G2G.GSGrid(VectorRBDisplay)
1868            vecGrid.SetTable(vecTable, True)
1869            if 'phoenix' in wx.version():
1870                vecGrid.Bind(wg.EVT_GRID_CELL_CHANGED, ChangeCell)
1871            else:
1872                vecGrid.Bind(wg.EVT_GRID_CELL_CHANGE, ChangeCell)
1873            if not imag:
1874                vecGrid.Bind(wg.EVT_GRID_CELL_LEFT_DCLICK, TypeSelect)
1875            attr = wx.grid.GridCellAttr()
1876            attr.SetEditor(G2G.GridFractionEditor(vecGrid))
1877            for c in range(3):
1878                vecGrid.SetColAttr(c, attr)
1879            for row in range(vecTable.GetNumberRows()):
1880                if imag:
1881                    vecGrid.SetCellStyle(row,3,VERY_LIGHT_GREY,True)                   
1882                for col in [4,5,6]:
1883                    vecGrid.SetCellStyle(row,col,VERY_LIGHT_GREY,True)
1884#            vecGrid.SetScrollRate(0,0)
1885            vecGrid.AutoSizeColumns(False)
1886            vecSizer.Add(vecGrid)
1887            return vecSizer
1888       
1889        def FillRefChoice(rbId,rbData):
1890            choiceIds = [i for i in range(len(rbData['rbTypes']))]
1891           
1892            rbRef = rbData.get('rbRef',[-1,-1,-1,False])
1893            for i in range(3):
1894                choiceIds.remove(rbRef[i])
1895            refChoice[rbId] = [choiceIds[:],choiceIds[:],choiceIds[:]]
1896            for i in range(3):
1897                refChoice[rbId][i].append(rbRef[i])
1898                refChoice[rbId][i].sort()     
1899           
1900        if VectorRB.GetSizer(): VectorRB.GetSizer().Clear(True)
1901        VectorRBSizer = wx.BoxSizer(wx.VERTICAL)
1902        for rbId in data['RBIds']['Vector']:
1903            if rbId != 'AtInfo':
1904                rbData = data['Vector'][rbId]
1905                FillRefChoice(rbId,rbData)
1906                VectorRBSizer.Add(rbNameSizer(rbId,rbData),0)
1907                VectorRBSizer.Add(rbRefAtmSizer(rbId,rbData),0)
1908                XYZ = np.array([[0.,0.,0.] for Ty in rbData['rbTypes']])
1909                for imag,mag in enumerate(rbData['VectMag']):
1910                    XYZ += mag*rbData['rbVect'][imag]
1911                    VectorRBSizer.Add(rbVectMag(rbId,imag,rbData),0)
1912                    VectorRBSizer.Add(rbVectors(rbId,imag,mag,XYZ,rbData),0)
1913                VectorRBSizer.Add((5,5),0)
1914                data['Vector'][rbId]['rbXYZ'] = XYZ       
1915        VectorRBSizer.Layout()   
1916        VectorRBDisplay.SetSizer(VectorRBSizer,True)
1917        Size = VectorRBSizer.GetMinSize()
1918        Size[0] += 40
1919        Size[1] = max(Size[1],450) + 20
1920        VectorRBDisplay.SetSize(Size)
1921        VectorRB.SetScrollbars(10,10,Size[0]/10-4,Size[1]/10-1)
1922        VectorRB.Scroll(0,Scroll)
1923       
1924    def UpdateResidueRB(rbId=0):
1925        AtInfo = data['Residue']['AtInfo']
1926        refChoice = {}
1927        RefObjs = []
1928
1929        def rbNameSizer(rbId,rbData):
1930
1931            def OnRBName(event):
1932                Obj = event.GetEventObject()
1933                rbData['RBname'] = Obj.GetValue()
1934                wx.CallAfter(UpdateResidueRB,rbId)
1935               
1936            def OnDelRB(event):
1937                Obj = event.GetEventObject()
1938                rbId = Indx[Obj.GetId()]
1939                if rbId in data['Residue']: 
1940                    del data['Residue'][rbId]
1941                    data['RBIds']['Residue'].remove(rbId)
1942                wx.CallAfter(UpdateResidueRB)
1943               
1944            def OnStripH(event):
1945                Obj = event.GetEventObject()
1946                rbId = Indx[Obj.GetId()]
1947                if rbId in data['Residue']:
1948                    newNames = []
1949                    newTypes = []
1950                    newXYZ = []
1951                    for i,atype in enumerate(rbData['rbTypes']):
1952                        if atype != 'H':
1953                            newNames.append(rbData['atNames'][i])
1954                            newTypes.append(rbData['rbTypes'][i])
1955                            newXYZ.append(rbData['rbXYZ'][i])
1956                    rbData['atNames'] = newNames
1957                    rbData['rbTypes'] = newTypes
1958                    rbData['rbXYZ'] = newXYZ
1959                G2plt.PlotRigidBody(G2frame,'Residue',AtInfo,rbData,plotDefaults)
1960                wx.CallAfter(UpdateResidueRB,rbId)
1961                   
1962            def OnPlotRB(event):
1963                Obj = event.GetEventObject()
1964                Obj.SetValue(False)
1965                G2plt.PlotRigidBody(G2frame,'Residue',AtInfo,rbData,plotDefaults)
1966           
1967            nameSizer = wx.BoxSizer(wx.HORIZONTAL)
1968            nameSizer.Add(wx.StaticText(ResidueRBDisplay,-1,'Residue name: '),
1969                0,wx.ALIGN_CENTER_VERTICAL)
1970            RBname = wx.TextCtrl(ResidueRBDisplay,-1,rbData['RBname'])
1971            Indx[RBname.GetId()] = rbId
1972            RBname.Bind(wx.EVT_TEXT_ENTER,OnRBName)
1973            RBname.Bind(wx.EVT_KILL_FOCUS,OnRBName)
1974            nameSizer.Add(RBname,0,wx.ALIGN_CENTER_VERTICAL)
1975            nameSizer.Add((5,0),)
1976            plotRB = wx.CheckBox(ResidueRBDisplay,-1,'Plot?')
1977            Indx[plotRB.GetId()] = rbId
1978            plotRB.Bind(wx.EVT_CHECKBOX,OnPlotRB)
1979            nameSizer.Add(plotRB,0,wx.ALIGN_CENTER_VERTICAL)
1980            nameSizer.Add((5,0),)
1981            if not rbData['useCount']:
1982                delRB = wx.CheckBox(ResidueRBDisplay,-1,'Delete?')
1983                Indx[delRB.GetId()] = rbId
1984                delRB.Bind(wx.EVT_CHECKBOX,OnDelRB)
1985                nameSizer.Add(delRB,0,wx.ALIGN_CENTER_VERTICAL)
1986                if 'H'  in rbData['rbTypes']:
1987                    stripH = wx.CheckBox(ResidueRBDisplay,-1,'Strip H-atoms?')
1988                    Indx[stripH.GetId()] = rbId
1989                    stripH.Bind(wx.EVT_CHECKBOX,OnStripH)
1990                    nameSizer.Add(stripH,0,wx.ALIGN_CENTER_VERTICAL)
1991            return nameSizer
1992           
1993        def rbResidues(rbId,rbData):
1994           
1995            def TypeSelect(event):
1996                AtInfo = data['Residue']['AtInfo']
1997                r,c = event.GetRow(),event.GetCol()
1998                if vecGrid.GetColLabelValue(c) == 'Type':
1999                    PE = G2elemGUI.PickElement(G2frame,oneOnly=True)
2000                    if PE.ShowModal() == wx.ID_OK:
2001                        if PE.Elem != 'None':
2002                            El = PE.Elem.strip().lower().capitalize()
2003                            if El not in AtInfo:
2004                                Info = G2elem.GetAtomInfo(El)
2005                                AtInfo[El] = [Info['Drad']['Color']]
2006                            rbData['rbTypes'][r] = El
2007                            vecGrid.SetCellValue(r,c,El)
2008                    PE.Destroy()
2009
2010            def ChangeCell(event):
2011                r,c =  event.GetRow(),event.GetCol()
2012                if r >= 0 and (0 <= c < 3):
2013                    try:
2014                        val = float(vecGrid.GetCellValue(r,c))
2015                        rbData['rbXYZ'][r][c] = val
2016                    except ValueError:
2017                        pass
2018                       
2019            def RowSelect(event):
2020                r,c =  event.GetRow(),event.GetCol()
2021                if c < 0:                   #only row clicks
2022                    for vecgrid in resList:
2023                        vecgrid.ClearSelection()
2024                    vecGrid.SelectRow(r,True)
2025
2026            def OnRefSel(event):
2027               
2028                Obj = event.GetEventObject()
2029                iref,res,jref = Indx[Obj.GetId()]
2030                sel = Obj.GetValue()
2031                ind = atNames.index(sel)
2032                if rbData['rbTypes'][ind] == 'H':
2033                    G2G.G2MessageBox(G2frame,'You should not select an H-atom for rigid body orientation')
2034                rbData['rbRef'][iref] = ind
2035                FillRefChoice(rbId,rbData)
2036                for i,ref in enumerate(RefObjs[jref]):
2037                    ref.SetItems([atNames[j] for j in refChoice[rbId][i]])
2038                    ref.SetValue(atNames[rbData['rbRef'][i]])                   
2039                rbXYZ = rbData['rbXYZ']
2040                if not iref:     #origin change
2041                    rbXYZ -= rbXYZ[ind]
2042                #TODO - transform all atom XYZ by axis choices
2043                Xxyz = rbXYZ[rbData['rbRef'][1]]
2044                X = Xxyz/np.sqrt(np.sum(Xxyz**2))
2045                Yxyz = rbXYZ[rbData['rbRef'][2]]
2046                Y = Yxyz/np.sqrt(np.sum(Yxyz**2))
2047                Mat = G2mth.getRBTransMat(X,Y)
2048                rbXYZ = np.inner(Mat,rbXYZ).T
2049                rbData['rbXYZ'] = rbXYZ
2050                res.ClearSelection()
2051                resTable = res.GetTable()
2052                for r in range(res.GetNumberRows()):
2053                    row = resTable.GetRowValues(r)
2054                    row[2:4] = rbXYZ[r]
2055                    resTable.SetRowValues(r,row)
2056                res.ForceRefresh()
2057                G2plt.PlotRigidBody(G2frame,'Residue',AtInfo,rbData,plotDefaults)
2058               
2059            Types = 2*[wg.GRID_VALUE_STRING,]+3*[wg.GRID_VALUE_FLOAT+':10,5',]
2060            colLabels = ['Name','Type','Cart x','Cart y','Cart z']
2061            table = []
2062            rowLabels = []
2063            for ivec,xyz in enumerate(rbData['rbXYZ']):
2064                table.append([rbData['atNames'][ivec],]+[rbData['rbTypes'][ivec],]+list(xyz))
2065                rowLabels.append(str(ivec))
2066            vecTable = G2G.Table(table,rowLabels=rowLabels,colLabels=colLabels,types=Types)
2067            vecGrid = G2G.GSGrid(ResidueRBDisplay)
2068            Indx[vecGrid.GetId()] = rbId
2069            resList.append(vecGrid)
2070            vecGrid.SetTable(vecTable, True)
2071            if 'phoenix' in wx.version():
2072                vecGrid.Bind(wg.EVT_GRID_CELL_CHANGED, ChangeCell)
2073            else:
2074                vecGrid.Bind(wg.EVT_GRID_CELL_CHANGE, ChangeCell)
2075            vecGrid.Bind(wg.EVT_GRID_CELL_LEFT_DCLICK, TypeSelect)
2076            vecGrid.Bind(wg.EVT_GRID_LABEL_LEFT_CLICK, RowSelect)
2077            attr = wx.grid.GridCellAttr()
2078            attr.SetEditor(G2G.GridFractionEditor(vecGrid))
2079            for c in range(3):
2080                vecGrid.SetColAttr(c, attr)
2081            for row in range(vecTable.GetNumberRows()):
2082                for col in range(5):
2083                    vecGrid.SetCellStyle(row,col,VERY_LIGHT_GREY,True)
2084            vecGrid.AutoSizeColumns(False)
2085            vecSizer = wx.BoxSizer()
2086            vecSizer.Add(vecGrid)
2087           
2088            refAtmSizer = wx.BoxSizer(wx.HORIZONTAL)
2089            atNames = rbData['atNames']
2090            rbRef = rbData['rbRef']
2091            if rbData['rbRef'][3] or rbData['useCount']:
2092                refAtmSizer.Add(wx.StaticText(ResidueRBDisplay,-1,
2093                    'Orientation reference non-H atoms A-B-C: %s, %s, %s'%(atNames[rbRef[0]], \
2094                     atNames[rbRef[1]],atNames[rbRef[2]])),0)
2095            else:
2096                refAtmSizer.Add(wx.StaticText(ResidueRBDisplay,-1,
2097                    'Orientation reference non-H atoms A-B-C: '),0,wx.ALIGN_CENTER_VERTICAL)
2098                refObj = [0,0,0]
2099                for i in range(3):
2100                    choices = [atNames[j] for j in refChoice[rbId][i]]
2101                    refSel = wx.ComboBox(ResidueRBDisplay,-1,value='',
2102                        choices=choices,style=wx.CB_READONLY|wx.CB_DROPDOWN)
2103                    refSel.SetValue(atNames[rbRef[i]])
2104                    refSel.Bind(wx.EVT_COMBOBOX, OnRefSel)
2105                    Indx[refSel.GetId()] = [i,vecGrid,len(RefObjs)]
2106                    refObj[i] = refSel
2107                    refAtmSizer.Add(refSel,0,wx.ALIGN_CENTER_VERTICAL)
2108                RefObjs.append(refObj)
2109           
2110            mainSizer = wx.BoxSizer(wx.VERTICAL)
2111            mainSizer.Add(refAtmSizer)
2112            mainSizer.Add(vecSizer)
2113            return mainSizer
2114           
2115        def SeqSizer(angSlide,rbId,iSeq,Seq,atNames):
2116           
2117            def ChangeAngle(event):
2118                event.Skip()
2119                Obj = event.GetEventObject()
2120                rbId,Seq = Indx[Obj.GetId()][:2]
2121                val = Seq[2]
2122                try:
2123                    val = float(Obj.GetValue())
2124                    Seq[2] = val
2125                except ValueError:
2126                    pass
2127                Obj.SetValue('%8.2f'%(val))
2128                G2plt.PlotRigidBody(G2frame,'Residue',AtInfo,data['Residue'][rbId],plotDefaults)
2129               
2130            def OnRadBtn(event):
2131                Obj = event.GetEventObject()
2132                Seq,iSeq,angId = Indx[Obj.GetId()]
2133                data['Residue'][rbId]['SelSeq'] = [iSeq,angId]
2134                angSlide.SetValue(int(100*Seq[2]))
2135               
2136            def OnDelBtn(event):
2137                Obj = event.GetEventObject()
2138                rbId,Seq = Indx[Obj.GetId()]
2139                data['Residue'][rbId]['rbSeq'].remove(Seq)       
2140                wx.CallAfter(UpdateResidueRB,rbId)
2141           
2142            seqSizer = wx.FlexGridSizer(0,5,2,2)
2143            seqSizer.AddGrowableCol(3,0)
2144            iBeg,iFin,angle,iMove = Seq
2145            ang = wx.TextCtrl(ResidueRBDisplay,-1,'%8.2f'%(angle),size=(50,20))
2146            if not iSeq:
2147                radBt = wx.RadioButton(ResidueRBDisplay,-1,'',style=wx.RB_GROUP)
2148                data['Residue'][rbId]['SelSeq'] = [iSeq,ang.GetId()]
2149            else:
2150                radBt = wx.RadioButton(ResidueRBDisplay,-1,'')
2151            radBt.Bind(wx.EVT_RADIOBUTTON,OnRadBtn)                   
2152            seqSizer.Add(radBt)
2153            delBt = wx.RadioButton(ResidueRBDisplay,-1,'')
2154            delBt.Bind(wx.EVT_RADIOBUTTON,OnDelBtn)
2155            seqSizer.Add(delBt)
2156            bond = wx.TextCtrl(ResidueRBDisplay,-1,'%s %s'%(atNames[iBeg],atNames[iFin]),size=(50,20))
2157            seqSizer.Add(bond,0,wx.ALIGN_CENTER_VERTICAL)
2158            Indx[radBt.GetId()] = [Seq,iSeq,ang.GetId()]
2159            Indx[delBt.GetId()] = [rbId,Seq]
2160            Indx[ang.GetId()] = [rbId,Seq,ang]
2161            ang.Bind(wx.EVT_TEXT_ENTER,ChangeAngle)
2162            ang.Bind(wx.EVT_KILL_FOCUS,ChangeAngle)
2163            seqSizer.Add(ang,0,wx.ALIGN_CENTER_VERTICAL)
2164            atms = ''
2165            for i in iMove:   
2166                atms += ' %s,'%(atNames[i])
2167            moves = wx.TextCtrl(ResidueRBDisplay,-1,atms[:-1],size=(200,20))
2168            seqSizer.Add(moves,1,wx.ALIGN_CENTER_VERTICAL|wx.EXPAND|wx.RIGHT)
2169            return seqSizer
2170           
2171        def SlideSizer():
2172           
2173            def OnSlider(event):
2174                Obj = event.GetEventObject()
2175                rbData = Indx[Obj.GetId()]
2176                iSeq,angId = rbData['SelSeq']
2177                val = float(Obj.GetValue())/100.
2178                rbData['rbSeq'][iSeq][2] = val
2179                Indx[angId][2].SetValue('%8.2f'%(val))
2180                G2plt.PlotRigidBody(G2frame,'Residue',AtInfo,rbData,plotDefaults)
2181           
2182            slideSizer = wx.BoxSizer(wx.HORIZONTAL)
2183            slideSizer.Add(wx.StaticText(ResidueRBDisplay,-1,'Selected torsion angle:'),0)
2184            iSeq,angId = rbData['SelSeq']
2185            angSlide = wx.Slider(ResidueRBDisplay,-1,
2186                int(100*rbData['rbSeq'][iSeq][2]),0,36000,size=(200,20),
2187                style=wx.SL_HORIZONTAL)
2188            angSlide.Bind(wx.EVT_SLIDER, OnSlider)
2189            Indx[angSlide.GetId()] = rbData
2190            slideSizer.Add(angSlide,0)           
2191            return slideSizer,angSlide
2192           
2193        def FillRefChoice(rbId,rbData):
2194            choiceIds = [i for i in range(len(rbData['atNames']))]
2195            for seq in rbData['rbSeq']:
2196                for i in seq[3]:
2197                    try:
2198                        choiceIds.remove(i)
2199                    except ValueError:
2200                        pass
2201            rbRef = rbData['rbRef']
2202            for i in range(3):
2203                try:
2204                    choiceIds.remove(rbRef[i])
2205                except ValueError:
2206                    pass
2207            refChoice[rbId] = [choiceIds[:],choiceIds[:],choiceIds[:]]
2208            for i in range(3):
2209                refChoice[rbId][i].append(rbRef[i])
2210                refChoice[rbId][i].sort()
2211               
2212        def OnSelect(event):
2213            rbname = rbchoice[select.GetSelection()]
2214            rbId = RBnames[rbname]
2215            wx.CallLater(100,UpdateResidueRB,rbId)
2216           
2217        GS = ResidueRBDisplay.GetSizer()
2218        if GS: 
2219            try:        #get around a c++ error in wx 4.0; doing is again seems to be OK
2220                GS.Clear(True)
2221            except:
2222                GS.Clear(True)
2223       
2224        RBnames = {}
2225        for rbid in data['RBIds']['Residue']:
2226            RBnames.update({data['Residue'][rbid]['RBname']:rbid,})
2227        if not RBnames:
2228            return
2229        rbchoice = list(RBnames.keys())
2230        ResidueRBSizer = wx.BoxSizer(wx.VERTICAL)
2231        if len(RBnames) > 1:
2232            selSizer = wx.BoxSizer(wx.HORIZONTAL)
2233            selSizer.Add(wx.StaticText(ResidueRBDisplay,label=' Select residue to view:'),0)
2234            rbchoice.sort()
2235            select = wx.ComboBox(ResidueRBDisplay,choices=rbchoice)
2236            select.Bind(wx.EVT_COMBOBOX,OnSelect)
2237            selSizer.Add(select,0)
2238            ResidueRBSizer.Add(selSizer,0)
2239        if not rbId:
2240            rbId = RBnames[rbchoice[0]]
2241        rbData = data['Residue'][rbId]
2242        FillRefChoice(rbId,rbData)
2243        ResidueRBSizer.Add(rbNameSizer(rbId,rbData),0)
2244        ResidueRBSizer.Add(rbResidues(rbId,rbData),0)
2245        ResidueRBSizer.Add((5,5),0)
2246        if rbData['rbSeq']:
2247            slideSizer,angSlide = SlideSizer()
2248        if len(rbData['rbSeq']):
2249            ResidueRBSizer.Add(wx.StaticText(ResidueRBDisplay,-1,
2250                'Sel  Del  Bond             Angle      Riding atoms'),
2251                0,wx.ALIGN_CENTER_VERTICAL)                       
2252        for iSeq,Seq in enumerate(rbData['rbSeq']):
2253            ResidueRBSizer.Add(SeqSizer(angSlide,rbId,iSeq,Seq,rbData['atNames']))
2254        if rbData['rbSeq']:
2255            ResidueRBSizer.Add(slideSizer,)
2256
2257        ResidueRBSizer.Add((5,25),)
2258        ResidueRBSizer.Layout()   
2259        ResidueRBDisplay.SetSizer(ResidueRBSizer,True)
2260        ResidueRBDisplay.SetAutoLayout(True)
2261        Size = ResidueRBSizer.GetMinSize()
2262        ResidueRBDisplay.SetSize(Size)
2263        ResidueRBDisplay.Show()
2264       
2265    def SetStatusLine(text):
2266        G2frame.GetStatusBar().SetStatusText(text,1)                                     
2267
2268    G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.RigidBodyMenu)
2269    SetStatusLine('')
2270    UpdateVectorRB()
2271    G2frame.rbBook.Bind(wx.aui.EVT_AUINOTEBOOK_PAGE_CHANGED, OnPageChanged)
2272    wx.CallAfter(OnPageChanged,None)
Note: See TracBrowser for help on using the repository browser.