source: trunk/GSASIIconstrGUI.py @ 3735

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

oops got monoclinic A3 & A5 switched!

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