source: trunk/GSASIIconstrGUI.py @ 3721

Last change on this file since 3721 was 3721, checked in by vondreele, 4 years ago

put in (now commented out) new code for making lattice constraints for magnetic phases - still doesn't work correctly.

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