source: trunk/GSASIIconstrGUI.py @ 3462

Last change on this file since 3462 was 3462, checked in by toby, 3 years ago

move check of fractions/scale to OnRefine? and OnView?

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