source: trunk/GSASIIconstrGUI.py @ 2496

Last change on this file since 2496 was 2496, checked in by vondreele, 6 years ago

small modification to fix of post Refine crash in ConstrGUI

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