source: trunk/GSASIIconstrGUI.py @ 3510

Last change on this file since 3510 was 3510, checked in by svnjenkins, 5 years ago

CheckScalePhaseFractions?: use only on PWDR hists

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