source: trunk/GSASIIconstrGUI.py @ 3467

Last change on this file since 3467 was 3467, checked in by vondreele, 3 years ago

fix problem in constraints where a deleted constraint variable wasn't made available for making a new constraint

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