source: trunk/GSASIIconstrGUI.py @ 4424

Last change on this file since 4424 was 4421, checked in by toby, 5 years ago

add R.B. extractor; misc doc updates; plt bug after strip Hs; grid bug: uncompleted edits; mac horz. line fix

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Author Revision URL Id
File size: 151.3 KB
Line 
1# -*- coding: utf-8 -*-
2#GSASIIconstrGUI - constraint GUI routines
3########### SVN repository information ###################
4# $Date: 2020-05-16 13:55:12 +0000 (Sat, 16 May 2020) $
5# $Author: toby $
6# $Revision: 4421 $
7# $URL: trunk/GSASIIconstrGUI.py $
8# $Id: GSASIIconstrGUI.py 4421 2020-05-16 13:55:12Z toby $
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 copy
20import os.path
21import wx
22import wx.grid as wg
23import wx.lib.scrolledpanel as wxscroll
24import wx.lib.gridmovers as gridmovers
25import random as ran
26import numpy as np
27import numpy.ma as ma
28import numpy.linalg as nl
29import GSASIIpath
30GSASIIpath.SetVersionNumber("$Revision: 4421 $")
31import GSASIIElem as G2elem
32import GSASIIElemGUI as G2elemGUI
33import GSASIIstrIO as G2stIO
34import GSASIImapvars as G2mv
35import GSASIImath as G2mth
36import GSASIIlattice as G2lat
37import GSASIIdataGUI as G2gd
38import GSASIIctrlGUI as G2G
39import GSASIIplot as G2plt
40import GSASIIobj as G2obj
41import GSASIIspc as G2spc
42import GSASIIpy3 as G2py3
43import GSASIIphsGUI as G2phG
44import GSASIIscriptable as G2sc
45VERY_LIGHT_GREY = wx.Colour(235,235,235)
46WACV = wx.ALIGN_CENTER_VERTICAL
47
48class DragableRBGrid(wg.Grid):
49    '''Simple grid implentation for display of rigid body positions.
50
51    :param parent: frame or panel where grid will be placed
52    :param dict rb: dict with atom labels, types and positions
53    :param function onChange: a callback used every time a value in
54      rb is changed.
55    '''
56    def __init__(self, parent, rb, onChange=None):
57        #wg.Grid.__init__(self, parent, wx.ID_ANY,size=(-1,200))
58        wg.Grid.__init__(self, parent, wx.ID_ANY)
59        self.SetTable(RBDataTable(rb,onChange), True)
60        # Enable Row moving
61        gridmovers.GridRowMover(self)
62        self.Bind(gridmovers.EVT_GRID_ROW_MOVE, self.OnRowMove, self)
63        self.SetColSize(0, 60)
64        self.SetColSize(1, 35)
65        self.SetColSize(5, 40)
66        for r in range(len(rb['RBlbls'])):
67            self.SetReadOnly(r,0,isReadOnly=True)
68            self.SetCellEditor(r,2, wg.GridCellFloatEditor())
69            self.SetCellEditor(r,3, wg.GridCellFloatEditor())
70            self.SetCellEditor(r,4, wg.GridCellFloatEditor())
71
72    def OnRowMove(self,evt):
73        'called when a row move needs to take place'
74        frm = evt.GetMoveRow()          # Row being moved
75        to = evt.GetBeforeRow()         # Before which row to insert
76        self.GetTable().MoveRow(frm,to)
77       
78    def completeEdits(self):
79        'complete any outstanding edits'
80        if self.IsCellEditControlEnabled(): # complete any grid edits in progress
81            #if GSASIIpath.GetConfigValue('debug'): print ('Completing grid edit')
82            self.SaveEditControlValue()
83            self.HideCellEditControl()
84            self.DisableCellEditControl()
85           
86class RBDataTable(wg.GridTableBase):
87    '''A Table to support :class:`DragableRBGrid`
88    '''
89    def __init__(self,rb,onChange):
90        wg.GridTableBase.__init__(self)
91        self.colLabels = ['Label','Type','x','y','z','Select']
92        self.coords = rb['RBcoords']
93        self.labels = rb['RBlbls']
94        self.types = rb['RBtypes']
95        self.index = rb['RBindex']
96        self.select = rb['RBselection']
97        self.onChange = onChange
98
99    # required methods
100    def GetNumberRows(self):
101        return len(self.labels)
102    def GetNumberCols(self):
103        return len(self.colLabels)
104    def IsEmptyCell(self, row, col):
105        return False
106    def GetValue(self, row, col):
107        row = self.index[row]
108        if col == 0:
109            return self.labels[row]
110        elif col == 1:
111            return self.types[row]
112        elif col < 5:
113            return '{:.5f}'.format(self.coords[row][col-2])
114        elif col == 5:
115            return self.select[row]
116    def SetValue(self, row, col, value):
117        row = self.index[row]
118        if col == 0:
119            self.labels[row] = value
120        elif col == 1:
121            self.types[row] = value
122        elif col < 5:
123            self.coords[row][col-2] = float(value)
124        elif col == 5:
125            self.select[row] = value
126        if self.onChange:
127            self.onChange()
128    # implement boolean for selection
129    def GetTypeName(self, row, col):
130        if col==5:
131            return wg.GRID_VALUE_BOOL
132        else:
133            return wg.GRID_VALUE_STRING
134    def CanGetValueAs(self, row, col, typeName):
135        if col==5 and typeName != wg.GRID_VALUE_BOOL:
136            return False
137        return True
138
139    # Display column & row labels
140    def GetColLabelValue(self, col):
141        return self.colLabels[col]
142    def GetRowLabelValue(self,row):
143        return str(row+1)
144
145    # Implement "row movement" by updating the pointer array
146    def MoveRow(self,frm,to):
147        grid = self.GetView()
148        if grid:
149            move = self.index[frm]
150            del self.index[frm]
151            if frm > to:
152                self.index.insert(to,move)
153            else:
154                self.index.insert(to-1,move)
155           
156            # Notify the grid
157            grid.BeginBatch()
158            msg = wg.GridTableMessage(
159                    self, wg.GRIDTABLE_NOTIFY_ROWS_DELETED, frm, 1
160                    )
161            grid.ProcessTableMessage(msg)
162            msg = wg.GridTableMessage(
163                    self, wg.GRIDTABLE_NOTIFY_ROWS_INSERTED, to, 1
164                    )
165            grid.ProcessTableMessage(msg)
166            grid.EndBatch()
167        if self.onChange:
168            self.onChange()
169
170def MakeDrawAtom(data,atom):
171    'Convert atom to format needed to draw it'
172    generalData = data['General']
173    if generalData['Type'] in ['nuclear','faulted',]:
174        atomInfo = [atom[:2]+atom[3:6]+['1',]+['vdW balls',]+
175                    ['',]+[[255,255,255],]+atom[9:]+[[],[]]][0]
176    ct,cs = [1,8]         #type & color
177    atNum = generalData['AtomTypes'].index(atom[ct])
178    atomInfo[cs] = list(generalData['Color'][atNum])
179    return atomInfo
180
181class ConstraintDialog(wx.Dialog):
182    '''Window to edit Constraint values
183    '''
184    def __init__(self,parent,title,text,data,separator='*',varname="",varyflag=False):
185        wx.Dialog.__init__(self,parent,-1,'Edit '+title, 
186            pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE)
187        self.data = data[:]
188        self.newvar = [varname,varyflag]
189        panel = wx.Panel(self)
190        mainSizer = wx.BoxSizer(wx.VERTICAL)
191        topLabl = wx.StaticText(panel,-1,text)
192        mainSizer.Add((10,10),1)
193        mainSizer.Add(topLabl,0,wx.ALIGN_CENTER_VERTICAL|wx.LEFT,10)
194        mainSizer.Add((10,10),1)
195        dataGridSizer = wx.FlexGridSizer(cols=3,hgap=2,vgap=2)
196        self.OkBtn = wx.Button(panel,wx.ID_OK)
197        for id in range(len(self.data)):
198            lbl1 = lbl = str(self.data[id][1])
199            if lbl[-1] != '=': lbl1 = lbl + ' ' + separator + ' '
200            name = wx.StaticText(panel,wx.ID_ANY,lbl1,
201                                 style=wx.ALIGN_RIGHT)
202            scale = G2G.ValidatedTxtCtrl(panel,self.data[id],0,
203                                          typeHint=float,
204                                          OKcontrol=self.DisableOK)
205            dataGridSizer.Add(name,0,wx.LEFT|wx.RIGHT|wx.ALIGN_CENTER_VERTICAL,5)
206            dataGridSizer.Add(scale,0,wx.RIGHT,3)
207            if ':' in lbl:
208                dataGridSizer.Add(
209                    wx.StaticText(panel,-1,G2obj.fmtVarDescr(lbl)),
210                    0,wx.RIGHT|wx.ALIGN_CENTER_VERTICAL,3)
211            else:
212                dataGridSizer.Add((-1,-1))
213        if title == 'New Variable':
214            name = wx.StaticText(panel,wx.ID_ANY,"New variable's\nname (optional)",
215                                 style=wx.ALIGN_CENTER)
216            scale = G2G.ValidatedTxtCtrl(panel,self.newvar,0,
217                                          typeHint=str,notBlank=False)
218            dataGridSizer.Add(name,0,wx.LEFT|wx.RIGHT|wx.ALIGN_CENTER_VERTICAL,5)
219            dataGridSizer.Add(scale,0,wx.RIGHT|wx.ALIGN_CENTER_VERTICAL,3)
220            self.refine = wx.CheckBox(panel,label='Refine?')
221            self.refine.SetValue(self.newvar[1]==True)
222            self.refine.Bind(wx.EVT_CHECKBOX, self.OnCheckBox)
223            dataGridSizer.Add(self.refine,0,wx.RIGHT|wx.ALIGN_CENTER_VERTICAL,3)
224           
225        mainSizer.Add(dataGridSizer,0,wx.EXPAND)
226        self.OkBtn.Bind(wx.EVT_BUTTON, self.OnOk)
227        self.OkBtn.SetDefault()
228        cancelBtn = wx.Button(panel,wx.ID_CANCEL)
229        cancelBtn.Bind(wx.EVT_BUTTON, self.OnCancel)
230        btnSizer = wx.BoxSizer(wx.HORIZONTAL)
231        btnSizer.Add((20,20),1)
232        btnSizer.Add(self.OkBtn)
233        btnSizer.Add((20,20),1)
234        btnSizer.Add(cancelBtn)
235        btnSizer.Add((20,20),1)
236
237        mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP, 10)
238        panel.SetSizer(mainSizer)
239        panel.Fit()
240        self.Fit()
241        self.CenterOnParent()
242       
243    def DisableOK(self,setting):
244        if setting:
245            self.OkBtn.Enable()
246        else:
247            self.OkBtn.Disable()
248
249    def OnCheckBox(self,event):
250        self.newvar[1] = self.refine.GetValue()
251
252    def OnOk(self,event):
253        parent = self.GetParent()
254        parent.Raise()
255        self.EndModal(wx.ID_OK)             
256
257    def OnCancel(self,event):
258        parent = self.GetParent()
259        parent.Raise()
260        self.EndModal(wx.ID_CANCEL)             
261
262    def GetData(self):
263        return self.data
264       
265################################################################################
266#####  Constraints
267################################################################################           
268def UpdateConstraints(G2frame,data):
269    '''Called when Constraints tree item is selected.
270    Displays the constraints in the data window
271    '''
272       
273    def FindEquivVarb(name,nameList):
274        'Creates a list of variables appropriate to constrain with name'
275        outList = []
276        #phlist = []
277        items = name.split(':')
278        namelist = [items[2],]
279        if 'dA' in name:
280            namelist = ['dAx','dAy','dAz']
281        elif 'AU' in name:
282            namelist = ['AUiso','AU11','AU22','AU33','AU12','AU13','AU23']
283        elif 'AM' in name:
284            namelist = ['AMx','AMy','AMz']
285        elif items[-1] in ['A0','A1','A2','A3','A4','A5']:
286            namelist = ['A0','A1','A2','A3','A4','A5']
287        elif items[-1] in ['D11','D22','D33','D12','D13','D23']:
288            namelist = ['D11','D22','D33','D12','D13','D23']
289        elif 'Tm' in name:
290            namelist = ['Tmin','Tmax']
291        elif 'MX' in name or 'MY' in name or 'MZ' in name:
292            namelist = ['MXcos','MYcos','MZcos','MXsin','MYsin','MZsin']
293        elif 'mV' in name:
294            namelist = ['mV0','mV1','mV2']
295        elif 'RB' in name:
296            rbfx = 'RB'+items[2][2]
297            if 'T' in name and 'Tr' not in name:
298                namelist = [rbfx+'T11',rbfx+'T22',rbfx+'T33',rbfx+'T12',rbfx+'T13',rbfx+'T23']
299            if 'L' in name:
300                namelist = [rbfx+'L11',rbfx+'L22',rbfx+'L33',rbfx+'L12',rbfx+'L13',rbfx+'L23']
301            if 'S' in name:
302                namelist = [rbfx+'S12',rbfx+'S13',rbfx+'S21',rbfx+'S23',rbfx+'S31',rbfx+'S32',rbfx+'SAA',rbfx+'SBB']
303            if 'U' in name:
304                namelist = [rbfx+'U',]
305
306        for item in nameList:
307            keys = item.split(':')
308            #if keys[0] not in phlist:
309            #    phlist.append(keys[0])
310            if items[1] == '*' and keys[2] in namelist: # wildcard -- select only sequential options
311                keys[1] = '*'
312                mitem = ':'.join(keys)
313                if mitem == name: continue
314                if mitem not in outList: outList.append(mitem)
315            elif keys[2] in namelist and item != name:
316                outList.append(item)
317        return outList
318       
319    def SelectVarbs(page,FrstVarb,varList,legend,constType):
320        '''Select variables used in constraints after one variable has
321        been selected. This routine determines the appropriate variables to be
322        used based on the one that has been selected and asks for more to be added.
323
324        It then creates the constraint and adds it to the constraints list.
325       
326        Called from OnAddEquivalence, OnAddFunction & OnAddConstraint (all but
327        OnAddHold)
328
329        :param list page: defines calling page -- type of variables to be used
330        :parm GSASIIobj.G2VarObj FrstVarb: reference to first selected variable
331        :param list varList: list of other appropriate variables to select from
332        :param str legend: header for selection dialog
333        :param str constType: type of constraint to be generated
334        :returns: a constraint, as defined in
335          :ref:`GSASIIobj <Constraint_definitions_table>`
336        '''
337        choices = [[i]+list(G2obj.VarDescr(i)) for i in varList]
338        meaning = G2obj.getDescr(FrstVarb.name)
339        if not meaning:
340            meaning = "(no definition found!)"
341        l = str(FrstVarb).split(':')
342        # make lists of phases & histograms to iterate over
343        phaselist = [l[0]]
344        if l[0]:
345            phaselbl = ['phase #'+l[0]]
346            if len(Phases) > 1:
347                phaselist += ['all'] 
348                phaselbl += ['all phases']
349        else:
350            phaselbl = ['']
351        histlist = [l[1]]
352        if l[1] == '*':
353            pass
354        elif l[1]:
355            histlbl = ['histogram #'+l[1]]
356            if len(Histograms) > 1:
357                histlist += ['all']
358                histlbl += ['all histograms']
359                typ = Histograms[G2obj.LookupHistName(l[1])[0]]['Instrument Parameters'][0]['Type'][1]
360                i = 0
361                for hist in Histograms:
362                    if Histograms[hist]['Instrument Parameters'][0]['Type'][1] == typ: i += 1
363                if i > 1:
364                    histlist += ['all='+typ]
365                    histlbl += ['all '+typ+' histograms']
366        else:
367            histlbl = ['']
368        # make a list of equivalent parameter names
369        nameList = [FrstVarb.name]
370        for var in varList:
371            nam = var.split(":")[2]
372            if nam not in nameList: nameList += [nam]
373        # add "wild-card" names to the list of variables
374        if l[1] == '*':
375            pass
376        elif page[1] == 'phs':
377            if 'RB' in FrstVarb.name:
378                pass
379            elif FrstVarb.atom is None:
380                for nam in nameList:
381                    for ph,plbl in zip(phaselist,phaselbl):
382                        if plbl: plbl = 'For ' + plbl
383                        var = ph+"::"+nam
384                        if var == str(FrstVarb) or var in varList: continue
385                        varList += [var]
386                        choices.append([var,plbl,meaning])
387            else:
388                for nam in nameList:
389                    for ph,plbl in zip(phaselist,phaselbl):
390                        if plbl: plbl = ' in ' + plbl
391                        for atype in ['']+TypeList:
392                            if atype:
393                                albl = "For "+atype+" atoms"
394                                akey = "all="+atype                       
395                            else:
396                                albl = "For all atoms"
397                                akey = "all"
398                            var = ph+"::"+nam+":"+akey
399                            if var == str(FrstVarb) or var in varList: continue
400                            varList += [var]
401                            choices.append([var,albl+plbl,meaning])
402        elif page[1] == 'hap':
403            if FrstVarb.name == "Scale":
404                meaning = "Phase fraction"
405            for nam in nameList:
406                for ph,plbl in zip(phaselist,phaselbl):
407                    if plbl: plbl = 'For ' + plbl
408                    for hst,hlbl in zip(histlist,histlbl):
409                        if hlbl:
410                            if plbl:
411                                hlbl = ' in ' + hlbl
412                            else:
413                                hlbl = 'For ' + hlbl                               
414                            var = ph+":"+hst+":"+nam
415                            if var == str(FrstVarb) or var in varList: continue
416                            varList += [var]
417                            choices.append([var,plbl+hlbl,meaning])
418        elif page[1] == 'hst':
419            if FrstVarb.name == "Scale":
420                meaning = "Scale factor"
421            for nam in nameList:
422                for hst,hlbl in zip(histlist,histlbl):
423                    if hlbl:
424                        hlbl = 'For ' + hlbl                               
425                        var = ":"+hst+":"+nam
426                        if var == str(FrstVarb) or var in varList: continue
427                        varList += [var]
428                        choices.append([var,hlbl,meaning])
429        elif page[1] == 'glb' or page[1] == 'sym':
430            pass
431        else:
432            raise Exception('Unknown constraint page '+ page[1])                   
433        if len(choices):
434            l1 = l2 = 1
435            for i1,i2,i3 in choices:
436                l1 = max(l1,len(i1))
437                l2 = max(l2,len(i2))
438            fmt = "{:"+str(l1)+"s} {:"+str(l2)+"s} {:s}"
439            atchoices = [fmt.format(*i1) for i1 in choices] # reformat list as str with columns
440            dlg = G2G.G2MultiChoiceDialog(
441                G2frame,legend,
442                'Constrain '+str(FrstVarb)+' with...',atchoices,
443                toggle=False,size=(625,400),monoFont=True)
444            dlg.CenterOnParent()
445            res = dlg.ShowModal()
446            Selections = dlg.GetSelections()[:]
447            dlg.Destroy()
448            if res != wx.ID_OK: return []
449            if len(Selections) == 0:
450                dlg = wx.MessageDialog(
451                    G2frame,
452                    'No variables were selected to include with '+str(FrstVarb),
453                    'No variables')
454                dlg.CenterOnParent()
455                dlg.ShowModal()
456                dlg.Destroy()
457                return []
458        else:
459            dlg = wx.MessageDialog(
460                G2frame,
461                'There are no appropriate variables to include with '+str(FrstVarb),
462                'No variables')
463            dlg.CenterOnParent()
464            dlg.ShowModal()
465            dlg.Destroy()
466            return []
467        # now process the variables provided by the user
468        varbs = [str(FrstVarb),] # list of selected variables
469        for sel in Selections:
470            var = varList[sel]
471            # phase(s) included
472            l = var.split(':')
473            if l[0] == "all":
474                phlist = [str(Phases[phase]['pId']) for phase in Phases]
475            else:
476                phlist = [l[0]]
477            # histogram(s) included
478            if l[1] == "all":
479                hstlist = [str(Histograms[hist]['hId']) for hist in Histograms]
480            elif '=' in l[1]:
481                htyp = l[1].split('=')[1]
482                hstlist = [str(Histograms[hist]['hId']) for hist in Histograms if 
483                           Histograms[hist]['Instrument Parameters'][0]['Type'][1] == htyp]
484            else:
485                hstlist = [l[1]]
486            if len(l) == 3:
487                for ph in phlist:
488                    for hst in hstlist:
489                        var = ph + ":" + hst + ":" + l[2]
490                        if var in varbs: continue
491                        varbs.append(var)
492            else: # constraints with atoms or rigid bodies
493                if len(l) == 5: # rigid body parameter
494                    var = ':'.join(l)
495                    if var in varbs: continue
496                    varbs.append(var)
497                elif l[3] == "all":
498                    for ph in phlist:
499                        key = G2obj.LookupPhaseName(ph)[0]
500                        for hst in hstlist: # should be blank
501                            for iatm,at in enumerate(Phases[key]['Atoms']):
502                                var = ph + ":" + hst + ":" + l[2] + ":" + str(iatm)
503                                if var in varbs: continue
504                                varbs.append(var)
505                elif '=' in l[3]:
506                    for ph in phlist:
507                        key = G2obj.LookupPhaseName(ph)[0]
508                        cx,ct,cs,cia = Phases[key]['General']['AtomPtrs']
509                        for hst in hstlist: # should be blank
510                            atyp = l[3].split('=')[1]
511                            for iatm,at in enumerate(Phases[key]['Atoms']):
512                                if at[ct] != atyp: continue
513                                var = ph + ":" + hst + ":" + l[2] + ":" + str(iatm)
514                                if var in varbs: continue
515                                varbs.append(var)
516                else:
517                    for ph in phlist:
518                        key = G2obj.LookupPhaseName(ph)[0]
519                        for hst in hstlist: # should be blank
520                            var = ph + ":" + hst + ":" + l[2] + ":" + l[3]
521                            if var in varbs: continue
522                            varbs.append(var)
523        if len(varbs) >= 1 or 'constraint' in constType:
524            constr = [[1.0,FrstVarb]]
525            for item in varbs[1:]:
526                constr += [[1.0,G2obj.G2VarObj(item)]]
527            if 'equivalence' in constType:
528                return [constr+[None,None,'e']]
529            elif 'function' in constType:
530                return [constr+[None,False,'f']]
531            elif 'constraint' in constType:
532                return [constr+[1.0,None,'c']]
533            else:
534                raise Exception('Unknown constraint type: '+str(constType))
535        else:
536            dlg = wx.MessageDialog(
537                G2frame,
538                'There are no selected variables to include with '+str(FrstVarb),
539                'No variables')
540            dlg.CenterOnParent()
541            dlg.ShowModal()
542            dlg.Destroy()
543        return []
544   
545    def ConstraintsLoad(data,newcons=[]):
546        '''Load all constraints. Constraints based on symmetry (etc.)
547        are generated by running :func:`GSASIIstrIO.GetPhaseData`.
548        '''
549        G2mv.InitVars()
550        #Find all constraints
551        constraintSet = []
552        for key in data:
553            if key.startswith('_'): continue
554            constraintSet += data[key]
555        if newcons:
556            constraintSet = constraintSet + newcons
557        constDictList,fixedList,ignored = G2stIO.ProcessConstraints(constraintSet)
558        # generate symmetry constraints to check for conflicts
559        rigidbodyDict = G2frame.GPXtree.GetItemPyData(   
560            G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'Rigid bodies'))
561        rbIds = rigidbodyDict.get('RBIds',{'Vector':[],'Residue':[]})
562        rbVary,rbDict = G2stIO.GetRigidBodyModels(rigidbodyDict,Print=False)
563        Natoms,atomIndx,phaseVary,phaseDict,pawleyLookup,FFtables,BLtables,MFtables,maxSSwave = G2stIO.GetPhaseData(
564            Phases,RestraintDict=None,rbIds=rbIds,Print=False) # generates atom symmetry constraints
565        return constDictList,phaseDict,fixedList
566           
567    def ConstraintsCheck(data,newcons=[]):
568        '''Load constraints & check them for errors. Since error checking
569        can cause changes in constraints in case of repairable conflicts
570        between equivalences, reload the constraints again after the check.
571        This could probably be done more effectively, but only reloading when
572        needed, but the reload should be quick.
573        '''
574        constDictList,phaseDict,fixedList = ConstraintsLoad(data,newcons)
575        msg = G2mv.EvaluateMultipliers(constDictList,phaseDict)
576        if msg:
577            return 'Unable to interpret multiplier(s): '+msg,''
578        res = G2mv.CheckConstraints('',constDictList,fixedList)
579        # reload constraints in case any were merged in MoveConfEquiv
580        ConstraintsLoad(data,newcons)
581        return res
582
583    def CheckAddedConstraint(newcons):
584        '''Check a new constraint that has just been input.
585        If there is an error display a message and discard the last entry
586
587        Since the varylist is not available, no warning messages
588        should be generated here
589
590        :returns: True if constraint should be added
591        '''
592       
593        errmsg,warnmsg = ConstraintsCheck(data,newcons)
594        if errmsg:
595            G2frame.ErrorDialog('Constraint Error',
596                'Error with newly added constraint:\n'+errmsg+
597                '\nIgnoring newly added constraint',parent=G2frame)
598            # reset error status
599            errmsg,warnmsg = ConstraintsCheck(data)
600            if errmsg:
601                print (errmsg)
602                print (G2mv.VarRemapShow([],True))
603            return False
604        elif warnmsg:
605            print ('Unexpected contraint warning:\n'+warnmsg)
606        return True
607
608    def CheckChangedConstraint():
609        '''Check all constraints after an edit has been made.
610        If there is an error display a message and reject the change.
611
612        Since the varylist is not available, no warning messages
613        should be generated.
614       
615        :returns: True if the edit should be retained
616        '''
617        errmsg,warnmsg = ConstraintsCheck(data)
618        if errmsg:
619            G2frame.ErrorDialog('Constraint Error',
620                'Error after editing constraint:\n'+errmsg+
621                '\nDiscarding last constraint edit',parent=G2frame)
622            # reset error status
623            errmsg,warnmsg = ConstraintsCheck(data)
624            if errmsg:
625                print (errmsg)
626                print (G2mv.VarRemapShow([],True))
627            return False
628        elif warnmsg:
629            print ('Unexpected contraint warning:\n'+warnmsg)
630        return True
631             
632    def PageSelection(page):
633        'Decode page reference'
634        if page[1] == "phs":
635            vartype = "phase"
636            varList = G2obj.removeNonRefined(phaseList)  # remove any non-refinable prms from list
637            constrDictEnt = 'Phase'
638        elif page[1] == "hap":
639            vartype = "Histogram*Phase"
640            varList = G2obj.removeNonRefined(hapList)  # remove any non-refinable prms from list
641            constrDictEnt = 'HAP'
642        elif page[1] == "hst":
643            vartype = "Histogram"
644            varList = G2obj.removeNonRefined(histList)  # remove any non-refinable prms from list
645            constrDictEnt = 'Hist'
646        elif page[1] == "glb":
647            vartype = "Global"
648            varList = G2obj.removeNonRefined(globalList)   # remove any non-refinable prms from list
649
650            constrDictEnt = 'Global'
651        elif page[1] == "sym":
652            return None,None,None
653        else:
654            raise Exception('Should not happen!')
655        return vartype,varList,constrDictEnt
656
657    def OnAddHold(event):
658        '''Create a new Hold constraint
659
660        Hold constraints allows the user to select one variable (the list of available
661        variables depends on which tab is currently active).
662        '''
663        page = G2frame.Page
664        vartype,varList,constrDictEnt = PageSelection(page)
665        if vartype is None: return
666        varList = G2obj.SortVariables(varList)
667        title1 = "Hold "+vartype+" variable"
668        if not varList:
669            G2frame.ErrorDialog('No variables','There are no variables of type '+vartype,
670                parent=G2frame)
671            return
672        l2 = l1 = 1
673        for i in varList:
674            l1 = max(l1,len(i))
675            loc,desc = G2obj.VarDescr(i)
676            l2 = max(l2,len(loc))
677        fmt = "{:"+str(l1)+"s} {:"+str(l2)+"s} {:s}"
678        varListlbl = [fmt.format(i,*G2obj.VarDescr(i)) for i in varList]
679        #varListlbl = ["("+i+") "+G2obj.fmtVarDescr(i) for i in varList]
680        legend = "Select variables to hold (Will not be varied, even if vary flag is set)"
681        dlg = G2G.G2MultiChoiceDialog(G2frame,
682            legend,title1,varListlbl,toggle=False,size=(625,400),monoFont=True)
683        dlg.CenterOnParent()
684        if dlg.ShowModal() == wx.ID_OK:
685            for sel in dlg.GetSelections():
686                Varb = varList[sel]
687                VarObj = G2obj.G2VarObj(Varb)
688                newcons = [[[0.0,VarObj],None,None,'h']]
689                if CheckAddedConstraint(newcons):
690                    data[constrDictEnt] += newcons
691        dlg.Destroy()
692        wx.CallAfter(OnPageChanged,None)
693       
694    def OnAddEquivalence(event):
695        '''add an Equivalence constraint'''
696        page = G2frame.Page
697        vartype,varList,constrDictEnt = PageSelection(page)
698        if vartype is None: return
699        title1 = "Create equivalence constraint between "+vartype+" variables"
700        title2 = "Select additional "+vartype+" variable(s) to be equivalent with "
701        if not varList:
702            G2frame.ErrorDialog('No variables','There are no variables of type '+vartype,
703                parent=G2frame)
704            return
705#        legend = "Select variables to make equivalent (only one of the variables will be varied when all are set to be varied)"
706        GetAddVars(page,title1,title2,varList,constrDictEnt,'equivalence')
707       
708    def OnAddAtomEquiv(event):
709        ''' Add equivalences between all parameters on atoms '''
710        page = G2frame.Page
711        vartype,varList,constrDictEnt = PageSelection(page)
712        if vartype is None: return
713        title1 = "Setup equivalent atom variables"
714        title2 = "Select additional atoms(s) to be equivalent with "
715        if not varList:
716            G2frame.ErrorDialog('No variables','There are no variables of type '+vartype,
717                parent=G2frame)
718            return
719#        legend = "Select atoms to make equivalent (only one of the atom variables will be varied when all are set to be varied)"
720        GetAddAtomVars(page,title1,title2,varList,constrDictEnt,'equivalence')
721       
722    def OnAddRiding(event):
723        ''' Add riding equivalences between all parameters on atoms '''
724        page = G2frame.Page
725        vartype,varList,constrDictEnt = PageSelection(page)
726        if vartype is None: return
727        title1 = "Setup riding atoms "
728        title2 = "Select additional atoms(s) to ride on "
729        if not varList:
730            G2frame.ErrorDialog('No variables','There are no variables of type '+vartype,
731                parent=G2frame)
732            return
733#        legend = "Select atoms to ride (only one of the atom variables will be varied when all are set to be varied)"
734        GetAddAtomVars(page,title1,title2,varList,constrDictEnt,'riding')
735   
736    def OnAddFunction(event):
737        '''add a Function (new variable) constraint'''
738        page = G2frame.Page
739        vartype,varList,constrDictEnt = PageSelection(page)
740        if vartype is None: return
741        title1 = "Setup new variable based on "+vartype+" variables"
742        title2 = "Include additional "+vartype+" variable(s) to be included with "
743        if not varList:
744            G2frame.ErrorDialog('No variables','There are no variables of type '+vartype,
745                parent=G2frame)
746            return
747#        legend = "Select variables to include in a new variable (the new variable will be varied when all included variables are varied)"
748        GetAddVars(page,title1,title2,varList,constrDictEnt,'function')
749                       
750    def OnAddConstraint(event):
751        '''add a constraint equation to the constraints list'''
752        page = G2frame.Page
753        vartype,varList,constrDictEnt = PageSelection(page)
754        if vartype is None: return
755        title1 = "Creating constraint on "+vartype+" variables"
756        title2 = "Select additional "+vartype+" variable(s) to include in constraint with "
757        if not varList:
758            G2frame.ErrorDialog('No variables','There are no variables of type '+vartype,
759                parent=G2frame)
760            return
761#        legend = "Select variables to include in a constraint equation (the values will be constrainted to equal a specified constant)"
762        GetAddVars(page,title1,title2,varList,constrDictEnt,'constraint')
763
764    def GetAddVars(page,title1,title2,varList,constrDictEnt,constType):
765        '''Get the variables to be added for OnAddEquivalence, OnAddFunction,
766        and OnAddConstraint. Then create and check the constraint.
767        '''
768        #varListlbl = ["("+i+") "+G2obj.fmtVarDescr(i) for i in varList]
769        if constType == 'equivalence':
770            omitVars = G2mv.GetDependentVars()
771        else:
772            omitVars = []
773        varList = G2obj.SortVariables([i for i in varList if i not in omitVars])
774        l2 = l1 = 1
775        for i in varList:
776            l1 = max(l1,len(i))
777            loc,desc = G2obj.VarDescr(i)
778            l2 = max(l2,len(loc))
779        fmt = "{:"+str(l1)+"s} {:"+str(l2)+"s} {:s}"
780        varListlbl = [fmt.format(i,*G2obj.VarDescr(i)) for i in varList]
781        dlg = G2G.G2SingleChoiceDialog(G2frame,'Select 1st variable:',
782            title1,varListlbl,monoFont=True,size=(625,400))
783        dlg.CenterOnParent()
784        if dlg.ShowModal() == wx.ID_OK:
785            if constType == 'equivalence':
786                omitVars = G2mv.GetDependentVars() + G2mv.GetIndependentVars()
787            sel = dlg.GetSelection()
788            FrstVarb = varList[sel]
789            VarObj = G2obj.G2VarObj(FrstVarb)
790            moreVarb = G2obj.SortVariables(FindEquivVarb(FrstVarb,[i for i in varList if i not in omitVars]))
791            newcons = SelectVarbs(page,VarObj,moreVarb,title2+FrstVarb,constType)
792            if len(newcons) > 0:
793                if CheckAddedConstraint(newcons):
794                    data[constrDictEnt] += newcons
795        dlg.Destroy()
796        wx.CallAfter(OnPageChanged,None)
797                       
798    def FindNeighbors(phase,FrstName,AtNames):
799        General = phase['General']
800        cx,ct,cs,cia = General['AtomPtrs']
801        Atoms = phase['Atoms']
802        atNames = [atom[ct-1] for atom in Atoms]
803        Cell = General['Cell'][1:7]
804        Amat,Bmat = G2lat.cell2AB(Cell)
805        atTypes = General['AtomTypes']
806        Radii = np.array(General['BondRadii'])
807        AtInfo = dict(zip(atTypes,Radii)) #or General['BondRadii']
808        Orig = atNames.index(FrstName.split()[1])
809        OType = Atoms[Orig][ct]
810        XYZ = G2mth.getAtomXYZ(Atoms,cx)       
811        Neigh = []
812        Dx = np.inner(Amat,XYZ-XYZ[Orig]).T
813        dist = np.sqrt(np.sum(Dx**2,axis=1))
814        sumR = AtInfo[OType]+0.5    #H-atoms only!
815        IndB = ma.nonzero(ma.masked_greater(dist-0.85*sumR,0.))
816        for j in IndB[0]:
817            if j != Orig:
818                Neigh.append(AtNames[j])
819        return Neigh
820       
821    def GetAddAtomVars(page,title1,title2,varList,constrDictEnt,constType):
822        '''Get the atom variables to be added for OnAddAtomEquiv. Then create and
823        check the constraints. Riding for H atoms only.
824        '''
825        Atoms = {G2obj.VarDescr(i)[0]:[] for i in varList if 'Atom' in G2obj.VarDescr(i)[0]}
826        for item in varList:
827            atName = G2obj.VarDescr(item)[0]
828            if atName in Atoms:
829                Atoms[atName].append(item)
830        AtNames = list(Atoms.keys())
831        AtNames.sort()
832        dlg = G2G.G2SingleChoiceDialog(G2frame,'Select 1st atom:',
833            title1,AtNames,monoFont=True,size=(625,400))
834        dlg.CenterOnParent()
835        FrstAtom = ''
836        if dlg.ShowModal() == wx.ID_OK:
837            sel = dlg.GetSelection()
838            FrstAtom = AtNames[sel]
839            if 'riding' in constType:
840                phaseName = (FrstAtom.split(' in ')[1]).strip()
841                phase = Phases[phaseName]
842                AtNames = FindNeighbors(phase,FrstAtom,AtNames)
843            else:
844                AtNames.remove(FrstAtom)
845        dlg.Destroy()
846        if FrstAtom == '':
847            print ('no atom selected')
848            return
849        dlg = G2G.G2MultiChoiceDialog(
850            G2frame,title2+FrstAtom,
851            'Constrain '+str(FrstAtom)+' with...',AtNames,
852            toggle=False,size=(625,400),monoFont=True)
853        if dlg.ShowModal() == wx.ID_OK:
854            Selections = dlg.GetSelections()[:]
855        else:
856            print ('no target atom selected')
857            dlg.Destroy()
858            return
859        dlg.Destroy()
860        for name in Atoms[FrstAtom]:
861            newcons = []
862            constr = []
863            if 'riding' in constType:
864                if 'AUiso' in name:
865                    constr = [[1.0,G2obj.G2VarObj(name)]]
866                elif 'AU11' in name:
867                    pass
868                elif 'AU' not in name:
869                    constr = [[1.0,G2obj.G2VarObj(name)]]
870            else:
871                constr = [[1.0,G2obj.G2VarObj(name)]]
872            pref = ':'+name.rsplit(':',1)[0].split(':',1)[1]    #get stuff between phase id & atom id
873            for sel in Selections:
874                name2 = Atoms[AtNames[sel]][0]
875                pid = name2.split(':',1)[0]                     #get phase id for 2nd atom
876                id = name2.rsplit(':',1)[-1]                    #get atom no. for 2nd atom
877                if 'riding' in constType:
878                    pref = pid+pref
879                    if 'AUiso' in pref:
880                        parts = pref.split('AUiso')
881                        constr += [[1.2,G2obj.G2VarObj('%s:%s'%(parts[0]+'AUiso',id))]]
882                    elif 'AU' not in pref:
883                        constr += [[1.0,G2obj.G2VarObj('%s:%s'%(pref,id))]]
884                else:
885                    constr += [[1.0,G2obj.G2VarObj('%s:%s'%(pid+pref,id))]]
886            if not constr:
887                continue
888            if 'frac' in pref and 'riding' not in constType:
889                newcons = [constr+[1.0,None,'c']]
890            else:
891                newcons = [constr+[None,None,'e']]
892            if len(newcons) > 0:
893                if CheckAddedConstraint(newcons):
894                    data[constrDictEnt] += newcons
895        wx.CallAfter(OnPageChanged,None)
896                       
897    def MakeConstraintsSizer(name,pageDisplay):
898        '''Creates a sizer displaying all of the constraints entered of
899        the specified type.
900
901        :param str name: the type of constraints to be displayed ('HAP',
902          'Hist', 'Phase', 'Global', 'Sym-Generated')
903        :param wx.Panel pageDisplay: parent panel for sizer
904        :returns: wx.Sizer created by method
905        '''
906        if name == 'Sym-Generated':         #show symmetry generated constraints
907            Sizer1 =  wx.BoxSizer(wx.VERTICAL)
908            Sizer1.Add(wx.StaticText(pageDisplay,wx.ID_ANY,
909                                    'Equivalences generated based on cell/space group input'))
910            Sizer1.Add((-1,5))
911            Sizer = wx.FlexGridSizer(0,2,0,0)
912            Sizer1.Add(Sizer)
913            for sym in G2mv.GetSymEquiv():
914                Sizer.Add(wx.StaticText(pageDisplay,wx.ID_ANY,'EQUIV'),
915                           0,wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER|wx.RIGHT|wx.LEFT,3)
916                Sizer.Add(wx.StaticText(pageDisplay,wx.ID_ANY,sym))
917                Sizer.Add((-1,-1))
918                Sizer.Add((-1,2))
919            return Sizer1
920        constSizer = wx.FlexGridSizer(0,6,0,0)
921        maxlen = 50 # characters before wrapping a constraint
922        for Id,item in enumerate(data[name]):
923            refineflag = False
924            helptext = ""
925            eqString = ['',]
926            problemItem = False
927            for term in item[:-3]:
928                if str(term[1]) in G2mv.problemVars:
929                    problemItem = True
930            if item[-1] == 'h': # Hold on variable
931                constSizer.Add((-1,-1),0)              # blank space for edit button
932                typeString = 'FIXED'
933                var = str(item[0][1])
934                varMean = G2obj.fmtVarDescr(var)
935                eqString[-1] =  var +'   '
936                helptext = "Prevents variable:\n"+ var + " ("+ varMean + ")\nfrom being changed"
937            elif item[-1] == 'f' or item[-1] == 'e' or item[-1] == 'c': # not true on original-style (2011?) constraints
938                constEdit = wx.Button(pageDisplay,wx.ID_ANY,'Edit',style=wx.BU_EXACTFIT)
939                constEdit.Bind(wx.EVT_BUTTON,OnConstEdit)
940                constSizer.Add(constEdit,0,wx.LEFT|wx.RIGHT|wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER,1)            # edit button
941                Indx[constEdit.GetId()] = [Id,name]
942                if item[-1] == 'f':
943                    helptext = "A new variable"
944                    if item[-3]:
945                        helptext += " named "+str(item[-3])
946                    helptext += " is created from a linear combination of the following variables:\n"
947                    for term in item[:-3]:
948                        var = str(term[1])
949                        if len(eqString[-1]) > maxlen:
950                            eqString.append(' ')
951                        m = term[0]
952                        if eqString[-1] != '':
953                            if m >= 0:
954                                eqString[-1] += ' + '
955                            else:
956                                eqString[-1] += ' - '
957                                m = abs(m)
958                        if m == 1:
959                            eqString[-1] += '{:} '.format(var)
960                        else:
961                            eqString[-1] += '{:g}*{:} '.format(m,var)
962                        varMean = G2obj.fmtVarDescr(var)
963                        helptext += "\n" + var + " ("+ varMean + ")"
964                    # Add ISODISTORT help items
965                    if '_Explain' in data:
966                        # this ignores the phase number. TODO: refactor that in
967                        hlptxt = None
968                        try:
969                            hlptxt = data['_Explain'].get(item[-3])
970                        except TypeError:
971                            # note that phase RanId is item[-3].phase
972                            hlptxt = data['_Explain'].get(str(item[-3].phase)+item[-3].name)
973                        if hlptxt:
974                            helptext += '\n\n'
975                            helptext += hlptxt
976                    # typeString = 'NEWVAR'
977                    # if item[-3]:
978                    #     eqString[-1] += ' = '+item[-3]
979                    # else:
980                    #     eqString[-1] += ' = New Variable'
981                    if item[-3]:
982                        typeString = str(item[-3]) + ' = '
983                    else:
984                        typeString = 'New Variable = '
985                    #print 'refine',item[-2]
986                    refineflag = True
987                elif item[-1] == 'c':
988                    helptext = "The following variables constrained to equal a constant:"
989                    for term in item[:-3]:
990                        var = str(term[1])
991                        if len(eqString[-1]) > maxlen:
992                            eqString.append(' ')
993                        m = term[0]
994                        if eqString[-1] != '':
995                            if term[0] > 0:
996                                eqString[-1] += ' + '
997                            else:
998                                eqString[-1] += ' - '
999                                m = -term[0]
1000                        if m == 1:
1001                            eqString[-1] += '{:} '.format(var)
1002                        else:
1003                            eqString[-1] += '{:g}*{:} '.format(m,var)
1004                        varMean = G2obj.fmtVarDescr(var)
1005                        helptext += "\n" + var + " ("+ varMean + ")"
1006                    typeString = 'CONST'
1007                    eqString[-1] += ' = '+str(item[-3])
1008                elif item[-1] == 'e':
1009                    helptext = "The following variables are set to be equivalent, noting multipliers:"
1010                    normval = item[:-3][1][0]
1011                    for i,term in enumerate(item[:-3]):
1012                        var = str(term[1])
1013                        if term[0] == 0: term[0] = 1.0
1014                        if len(eqString[-1]) > maxlen:
1015                            eqString.append(' ')
1016                        if i == 0: # move independent variable to end
1017                            indepterm = term
1018                            continue
1019                        elif eqString[-1] != '':
1020                            eqString[-1] += ' = '
1021                        if normval/term[0] == 1:
1022                            eqString[-1] += '{:}'.format(var)
1023                        else:
1024                            eqString[-1] += '{:g}*{:} '.format(normval/term[0],var)
1025                        varMean = G2obj.fmtVarDescr(var)
1026                        helptext += "\n" + var + " ("+ varMean + ")"
1027                    if normval/indepterm[0] == 1:
1028                        eqString[-1] += ' = {:} '.format(indepterm[1])
1029                    else:
1030                        eqString[-1] += ' = {:g}*{:} '.format(normval/indepterm[0],str(indepterm[1]))
1031                    typeString = 'EQUIV'
1032                else:
1033                    print ('Unexpected constraint'+item)
1034               
1035            else:
1036                print ('Removing old-style constraints')
1037                data[name] = []
1038                return constSizer
1039            constDel = wx.Button(pageDisplay,wx.ID_ANY,'Delete',style=wx.BU_EXACTFIT)
1040            constDel.Bind(wx.EVT_BUTTON,OnConstDel)
1041            Indx[constDel.GetId()] = [Id,name]
1042            constSizer.Add(constDel,0,wx.LEFT|wx.RIGHT|wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER,1)             # delete button
1043            if helptext:
1044                ch = G2G.HelpButton(pageDisplay,helptext)
1045                constSizer.Add(ch,0,wx.LEFT|wx.RIGHT|wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER,1)
1046            else:
1047                constSizer.Add((-1,-1))
1048            if refineflag:
1049                ch = G2G.G2CheckBox(pageDisplay,'',item,-2)
1050                constSizer.Add(ch,0,wx.LEFT|wx.RIGHT|wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER,1)
1051            else:
1052                constSizer.Add((-1,-1))               
1053            constSizer.Add(wx.StaticText(pageDisplay,wx.ID_ANY,typeString),
1054                           0,wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER|wx.RIGHT|wx.LEFT,3)
1055            if problemItem: eqString[-1] += ' -- Conflict: see console'
1056            if len(eqString) > 1:
1057                Eq = wx.BoxSizer(wx.VERTICAL)
1058                for s in eqString:
1059                    line = wx.StaticText(pageDisplay,wx.ID_ANY,s)
1060                    if problemItem: line.SetBackgroundColour(wx.YELLOW)
1061                    Eq.Add(line,0,wx.ALIGN_CENTER_VERTICAL)
1062                Eq.Add((-1,4))
1063            else:
1064                Eq = wx.StaticText(pageDisplay,wx.ID_ANY,eqString[0])
1065                if problemItem: Eq.SetBackgroundColour(wx.YELLOW)
1066            constSizer.Add(Eq,1,wx.ALIGN_CENTER_VERTICAL)
1067        return constSizer
1068               
1069    def OnConstDel(event):
1070        'Delete a constraint'
1071        Obj = event.GetEventObject()
1072        Id,name = Indx[Obj.GetId()]
1073        del data[name][Id]
1074        ConstraintsLoad(data)
1075        wx.CallAfter(OnPageChanged,None)
1076
1077    def OnConstEdit(event):
1078        '''Called to edit an individual contraint in response to a
1079        click on its Edit button
1080        '''
1081        Obj = event.GetEventObject()
1082        Id,name = Indx[Obj.GetId()]
1083        if data[name][Id][-1] == 'f':
1084            items = data[name][Id][:-3]
1085            constType = 'New Variable'
1086            if data[name][Id][-3]:
1087                varname = str(data[name][Id][-3])
1088            else:
1089                varname = ""
1090            lbl = 'Enter value for each term in constraint; sum = new variable'
1091            dlg = ConstraintDialog(G2frame,constType,lbl,items,
1092                                   varname=varname,varyflag=data[name][Id][-2])
1093        elif data[name][Id][-1] == 'c':
1094            items = data[name][Id][:-3]+[
1095                [str(data[name][Id][-3]),'fixed value =']]
1096            constType = 'Constraint'
1097            lbl = 'Edit value for each term in constant constraint sum'
1098            dlg = ConstraintDialog(G2frame,constType,lbl,items)
1099        elif data[name][Id][-1] == 'e':
1100            items = data[name][Id][:-3]
1101            constType = 'Equivalence'
1102            lbl = 'The following terms are set to be equal:'
1103            dlg = ConstraintDialog(G2frame,constType,lbl,items,'/')
1104        else:
1105            return
1106        try:
1107            prev = copy.deepcopy(data[name][Id])
1108            if dlg.ShowModal() == wx.ID_OK:
1109                result = dlg.GetData()
1110                for i in range(len(data[name][Id][:-3])):
1111                    if type(data[name][Id][i]) is tuple: # fix non-mutable construct
1112                        data[name][Id][i] = list(data[name][Id][i])
1113                    data[name][Id][i][0] = result[i][0]
1114                if data[name][Id][-1] == 'c':
1115                    data[name][Id][-3] = str(result[-1][0])
1116                elif data[name][Id][-1] == 'f':
1117                    data[name][Id][-2] = dlg.newvar[1]
1118                    if type(data[name][Id][-3]) is str:
1119                        # process the variable name to put in global form (::var)
1120                        varname = str(dlg.newvar[0]).strip().replace(' ','_')
1121                        if varname.startswith('::'):
1122                            varname = varname[2:]
1123                        varname = varname.replace(':',';')
1124                        if varname:
1125                            data[name][Id][-3] = varname
1126                        else:
1127                            data[name][Id][-3] = ''
1128                if not CheckChangedConstraint():
1129                    data[name][Id] = prev
1130            else:
1131                data[name][Id] = prev
1132        except:
1133            import traceback
1134            print (traceback.format_exc())
1135        finally:
1136            dlg.Destroy()
1137        wx.CallAfter(OnPageChanged,None)
1138   
1139    def UpdateConstraintPanel(panel,typ):
1140        '''Update the contents of the selected Constraint
1141        notebook tab. Called in :func:`OnPageChanged`
1142        '''
1143        if panel.GetSizer(): panel.GetSizer().Clear(True)
1144        Siz = wx.BoxSizer(wx.VERTICAL)
1145        Siz.Add((5,5),0)
1146        Siz.Add(MakeConstraintsSizer(typ,panel),1,wx.EXPAND)
1147        panel.SetSizer(Siz,True)
1148        Size = Siz.GetMinSize()
1149        Size[0] += 40
1150        Size[1] = max(Size[1],450) + 20
1151        panel.SetSize(Size)
1152        panel.SetScrollbars(10,10,Size[0]/10-4,Size[1]/10-1)
1153        panel.Show()
1154
1155    def OnPageChanged(event):
1156        '''Called when a tab is pressed or when a "select tab" menu button is
1157        used (see RaisePage), or to refresh the current tab contents (event=None)
1158        '''
1159        if event:       #page change event!
1160            page = event.GetSelection()
1161        else: # called directly, get current page
1162            page = G2frame.constr.GetSelection()
1163        G2frame.constr.ChangeSelection(page)
1164        text = G2frame.constr.GetPageText(page)
1165        G2frame.dataWindow.ConstraintEdit.Enable(G2G.wxID_EQUIVALANCEATOMS,False)
1166#        G2frame.dataWindow.ConstraintEdit.Enable(G2G.wxID_ADDRIDING,False)
1167        if text == 'Histogram/Phase':
1168            enableEditCons = [False]+4*[True]
1169            G2frame.Page = [page,'hap']
1170            UpdateConstraintPanel(HAPConstr,'HAP')
1171        elif text == 'Histogram':
1172            enableEditCons = [False]+4*[True]
1173            G2frame.Page = [page,'hst']
1174            UpdateConstraintPanel(HistConstr,'Hist')
1175        elif text == 'Phase':
1176            enableEditCons = 5*[True]
1177            G2frame.Page = [page,'phs']
1178            G2frame.dataWindow.ConstraintEdit.Enable(G2G.wxID_EQUIVALANCEATOMS,True)
1179#            G2frame.dataWindow.ConstraintEdit.Enable(G2G.wxID_ADDRIDING,True)
1180            if 'DELETED' in str(PhaseConstr):   #seems to be no other way to do this (wx bug)
1181                if GSASIIpath.GetConfigValue('debug'):
1182                    print ('DBG_wx error: PhaseConstr not cleanly deleted after Refine')
1183                return
1184            UpdateConstraintPanel(PhaseConstr,'Phase')
1185        elif text == 'Global':
1186            enableEditCons = [False]+4*[True]
1187            G2frame.Page = [page,'glb']
1188            UpdateConstraintPanel(GlobalConstr,'Global')
1189        else:
1190            enableEditCons = 5*[False]
1191            G2frame.Page = [page,'sym']
1192            UpdateConstraintPanel(SymConstr,'Sym-Generated')
1193        # remove menu items when not allowed
1194        for obj,flag in zip(G2frame.dataWindow.ConstraintEdit.GetMenuItems(),enableEditCons): 
1195            obj.Enable(flag)
1196        G2frame.dataWindow.SetDataSize()
1197
1198    def RaisePage(event):
1199        'Respond to a "select tab" menu button'
1200        try:
1201            i = (G2G.wxID_CONSPHASE,
1202                 G2G.wxID_CONSHAP,
1203                 G2G.wxID_CONSHIST,
1204                 G2G.wxID_CONSGLOBAL,
1205                 G2G.wxID_CONSSYM,
1206                ).index(event.GetId())
1207            G2frame.constr.SetSelection(i)
1208            wx.CallAfter(OnPageChanged,None)
1209        except ValueError:
1210            print('Unexpected event in RaisePage')
1211
1212    def SetStatusLine(text):
1213        G2frame.GetStatusBar().SetStatusText(text,1)
1214
1215    # UpdateConstraints execution starts here
1216    if not data:
1217        data.update({'Hist':[],'HAP':[],'Phase':[],'Global':[]})       #empty dict - fill it
1218    if 'Global' not in data:                                            #patch
1219        data['Global'] = []
1220    # DEBUG code ########################################
1221    #import GSASIIconstrGUI
1222    #reload(GSASIIconstrGUI)
1223    #reload(G2obj)
1224    #reload(G2stIO)
1225    #import GSASIIstrMain
1226    #reload(GSASIIstrMain)   
1227    #reload(G2mv)
1228    #reload(G2gd)
1229    ###################################################
1230    Histograms,Phases = G2frame.GetUsedHistogramsAndPhasesfromTree()
1231    if not len(Phases) or not len(Histograms):
1232        dlg = wx.MessageDialog(G2frame,'You need both phases and histograms to see Constraints',
1233            'No phases or histograms')
1234        dlg.CenterOnParent()
1235        dlg.ShowModal()
1236        dlg.Destroy()
1237        return
1238    G2obj.IndexAllIds(Histograms,Phases)
1239    for p in Phases:
1240        if 'ISODISTORT' in Phases[p]:
1241            G2frame.dataWindow.ConstraintEdit.Enable(G2G.wxID_SHOWISO,True)
1242            break
1243    ##################################################################################
1244    # patch: convert old-style (str) variables in constraints to G2VarObj objects
1245    for key,value in data.items():
1246        if key.startswith('_'): continue
1247        j = 0
1248        for cons in value:
1249            #print cons             # DEBUG
1250            for i in range(len(cons[:-3])):
1251                if type(cons[i][1]) is str:
1252                    cons[i][1] = G2obj.G2VarObj(cons[i][1])
1253                    j += 1
1254        if j:
1255            print (str(key) + ': '+str(j)+' variable(s) as strings converted to objects')
1256    ##################################################################################
1257    rigidbodyDict = G2frame.GPXtree.GetItemPyData(
1258        G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'Rigid bodies'))
1259    rbIds = rigidbodyDict.get('RBIds',{'Vector':[],'Residue':[]})
1260    rbVary,rbDict = G2stIO.GetRigidBodyModels(rigidbodyDict,Print=False)
1261    badPhaseParms = ['Ax','Ay','Az','Amul','AI/A','Atype','SHorder','AwaveType','FwaveType','PwaveType','MwaveType','Vol','isMag',]
1262    globalList = list(rbDict.keys())
1263    globalList.sort()
1264    try:
1265        AtomDict = dict([Phases[phase]['pId'],Phases[phase]['Atoms']] for phase in Phases)
1266    except KeyError:
1267        G2frame.ErrorDialog('Constraint Error','Constraints cannot be set until a cycle of least squares'+
1268                            ' has been run.\nWe suggest you refine a scale factor.')
1269        return
1270
1271    # create a list of the phase variables
1272    Natoms,atomIndx,phaseVary,phaseDict,pawleyLookup,FFtable,BLtable,MFtable,maxSSwave = G2stIO.GetPhaseData(Phases,rbIds=rbIds,Print=False)
1273    phaseList = []
1274    for item in phaseDict:
1275        if item.split(':')[2] not in badPhaseParms:
1276            phaseList.append(item)
1277    phaseList.sort()
1278    phaseAtNames = {}
1279    phaseAtTypes = {}
1280    TypeList = []
1281    for item in phaseList:
1282        Split = item.split(':')
1283        if Split[2][:2] in ['AU','Af','dA','AM']:
1284            Id = int(Split[0])
1285            phaseAtNames[item] = AtomDict[Id][int(Split[3])][0]
1286            phaseAtTypes[item] = AtomDict[Id][int(Split[3])][1]
1287            if phaseAtTypes[item] not in TypeList:
1288                TypeList.append(phaseAtTypes[item])
1289        else:
1290            phaseAtNames[item] = ''
1291            phaseAtTypes[item] = ''
1292             
1293    # create a list of the hist*phase variables
1294    seqList = G2frame.testSeqRefineMode()
1295    if seqList: # for sequential refinement, only process 1st histgram in list
1296        histDict = {seqList[0]:Histograms[seqList[0]]}
1297    else:
1298        histDict = Histograms
1299    hapVary,hapDict,controlDict = G2stIO.GetHistogramPhaseData(Phases,histDict,Print=False,resetRefList=False)
1300    hapList = sorted([i for i in hapDict.keys() if i.split(':')[2] not in ('Type',)])
1301    if seqList: # convert histogram # to wildcard
1302        wildList = [] # list of variables with "*" for histogram number
1303        for i in hapList:
1304            s = i.split(':')
1305            if s[1] == "": continue
1306            s[1] = '*'
1307            sj = ':'.join(s)
1308            if sj not in wildList: wildList.append(sj)
1309        hapList = wildList
1310    histVary,histDict,controlDict = G2stIO.GetHistogramData(histDict,Print=False)
1311    histList = list(histDict.keys())
1312    histList.sort()
1313    if seqList: # convert histogram # to wildcard
1314        wildList = [] # list of variables with "*" for histogram number
1315        for i in histList:
1316            s = i.split(':')
1317            if s[1] == "": continue
1318            s[1] = '*'
1319            sj = ':'.join(s)
1320            if sj not in wildList: wildList.append(sj)
1321        histList = wildList       
1322    Indx = {}
1323    G2frame.Page = [0,'phs']
1324   
1325    G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.ConstraintMenu)
1326    SetStatusLine('')
1327   
1328    G2frame.Bind(wx.EVT_MENU, OnAddConstraint, id=G2G.wxID_CONSTRAINTADD)
1329    G2frame.Bind(wx.EVT_MENU, OnAddFunction, id=G2G.wxID_FUNCTADD)
1330    G2frame.Bind(wx.EVT_MENU, OnAddEquivalence, id=G2G.wxID_EQUIVADD)
1331    G2frame.Bind(wx.EVT_MENU, OnAddHold, id=G2G.wxID_HOLDADD)
1332    G2frame.Bind(wx.EVT_MENU, OnAddAtomEquiv, id=G2G.wxID_EQUIVALANCEATOMS)
1333#    G2frame.Bind(wx.EVT_MENU, OnAddRiding, id=G2G.wxID_ADDRIDING)
1334    def OnShowISODISTORT(event):
1335        ShowIsoDistortCalc(G2frame)
1336    G2frame.Bind(wx.EVT_MENU, OnShowISODISTORT, id=G2G.wxID_SHOWISO)
1337    # tab commands
1338    for id in (G2G.wxID_CONSPHASE,
1339               G2G.wxID_CONSHAP,
1340               G2G.wxID_CONSHIST,
1341               G2G.wxID_CONSGLOBAL,
1342               G2G.wxID_CONSSYM,
1343               ):
1344        G2frame.Bind(wx.EVT_MENU, RaisePage,id=id)
1345
1346    #G2frame.constr = G2G.GSNoteBook(parent=G2frame.dataWindow,size=G2frame.dataWindow.GetClientSize())
1347    G2frame.constr = G2G.GSNoteBook(parent=G2frame.dataWindow)
1348    G2frame.dataWindow.GetSizer().Add(G2frame.constr,1,wx.ALL|wx.EXPAND)
1349    # note that order of pages is hard-coded in RaisePage
1350    PhaseConstr = wx.ScrolledWindow(G2frame.constr)
1351    G2frame.constr.AddPage(PhaseConstr,'Phase')
1352    HAPConstr = wx.ScrolledWindow(G2frame.constr)
1353    G2frame.constr.AddPage(HAPConstr,'Histogram/Phase')
1354    HistConstr = wx.ScrolledWindow(G2frame.constr)
1355    G2frame.constr.AddPage(HistConstr,'Histogram')
1356    GlobalConstr = wx.ScrolledWindow(G2frame.constr)
1357    G2frame.constr.AddPage(GlobalConstr,'Global')
1358    SymConstr = wx.ScrolledWindow(G2frame.constr)
1359    G2frame.constr.AddPage(SymConstr,'Sym-Generated')
1360    wx.CallAfter(OnPageChanged,None)
1361    G2frame.constr.Bind(wx.aui.EVT_AUINOTEBOOK_PAGE_CHANGED, OnPageChanged)
1362    # validate all the constrants -- should not see any errors here normally
1363    errmsg,warnmsg = ConstraintsCheck(data)
1364    if errmsg:
1365        G2frame.ErrorDialog('Constraint Error',
1366                            'Error in constraints:\n'+errmsg+'\nCheck console output for more information',
1367                            parent=G2frame)
1368        print (errmsg)
1369        print (G2mv.VarRemapShow([],True))
1370    elif warnmsg:
1371        print ('Unexpected contraint warning:\n'+warnmsg)
1372       
1373################################################################################
1374# check scale & phase fractions, create constraint if needed
1375################################################################################
1376def CheckAllScalePhaseFractions(G2frame):
1377    '''Check if scale factor and all phase fractions are refined without a constraint
1378    for all used histograms, if so, offer the user a chance to create a constraint
1379    on the sum of phase fractions
1380    '''
1381    histograms, phases = G2frame.GetUsedHistogramsAndPhasesfromTree()
1382    for i,hist in enumerate(histograms):
1383        CheckScalePhaseFractions(G2frame,hist,histograms,phases)
1384       
1385def CheckScalePhaseFractions(G2frame,hist,histograms,phases):
1386    '''Check if scale factor and all phase fractions are refined without a constraint
1387    for histogram hist, if so, offer the user a chance to create a constraint
1388    on the sum of phase fractions
1389    '''
1390    if G2frame.testSeqRefineMode():
1391        histStr = '*'
1392    else:
1393        histStr = str(histograms[hist]['hId'])
1394    # Is this powder?
1395    if not hist.startswith('PWDR '): return
1396    # do this only if the scale factor is varied
1397    if not histograms[hist]['Sample Parameters']['Scale'][1]: return
1398    # are all phase fractions varied in all used histograms?
1399    phaseCount = 0
1400    for p in phases:
1401        if hist not in phases[p]['Histograms']: continue
1402        if phases[p]['Histograms'][hist]['Use'] and not phases[p]['Histograms'][hist]['Scale'][1]:
1403            return
1404        else:
1405            phaseCount += 1
1406   
1407    # all phase fractions and scale factor varied, now scan through constraints
1408    sub = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'Constraints') 
1409    Constraints = G2frame.GPXtree.GetItemPyData(sub)
1410    for c in Constraints.get('HAP',[]):
1411        if c[-1] != 'c': continue
1412        if not c[-3]: continue
1413        if len(c[:-3]) != phaseCount: continue
1414        # got a constraint equation with right number of terms, is it on phase fractions for
1415        # the correct histogram?
1416        if all([(i[1].name == 'Scale' and i[1].varname().split(':')[1] == histStr) for i in c[:-3]]):
1417            # got a constraint, this is OK
1418            return
1419    dlg = wx.MessageDialog(G2frame,'You are refining the scale factor and all phase fractions for histogram #'+
1420        histStr+'. This will produce an unstable refinement. '+
1421        'Do you want to constrain the sum of phase fractions?','Create constraint?',wx.OK|wx.CANCEL)
1422    if dlg.ShowModal() != wx.ID_OK:
1423        dlg.Destroy()
1424        return
1425    dlg.Destroy()
1426
1427    constr = []
1428    for p in phases:
1429        if hist not in phases[p]['Histograms']: continue
1430        if not phases[p]['Histograms'][hist]['Use']: continue
1431        constr += [[1.0,G2obj.G2VarObj(':'.join((str(phases[p]['pId']),histStr,'Scale')))]]
1432    constr += [1.0,None,'c']
1433    Constraints['HAP'] += [constr]
1434       
1435################################################################################
1436#### Make nuclear/magnetic phase transition constraints - called by OnTransform in G2phsGUI
1437################################################################################       
1438       
1439def TransConstraints(G2frame,oldPhase,newPhase,Trans,Vec,atCodes):
1440    '''Add constraints for new magnetic phase created via transformation of old
1441    nuclear one
1442    NB: A = [G11,G22,G33,2*G12,2*G13,2*G23]
1443    '''
1444   
1445    def SetUniqAj(pId,iA,SGData):
1446        SGLaue = SGData['SGLaue']
1447        SGUniq = SGData['SGUniq']
1448        if SGLaue in ['m3','m3m']:
1449            if iA in [0,1,2]:
1450                parm = '%d::%s'%(pId,'A0')
1451            else:
1452                parm = None
1453        elif SGLaue in ['4/m','4/mmm']:
1454            if iA in [0,1]:
1455                parm = '%d::%s'%(pId,'A0')
1456            elif iA == 2:
1457                parm = '%d::%s'%(pId,'A2')
1458            else:
1459                parm = None
1460        elif SGLaue in ['6/m','6/mmm','3m1', '31m', '3']:
1461            if iA in [0,1,3]:
1462                parm = '%d::%s'%(pId,'A0')
1463            elif iA == 2:
1464                parm = '%d::%s'%(pId,'A2')
1465            else:
1466                parm = None
1467        elif SGLaue in ['3R', '3mR']:
1468            if ia in [0,1,2]:
1469                parm = '%d::%s'%(pId,'A0')
1470            else:
1471                parm = '%d::%s'%(pId,'A3')
1472        elif SGLaue in ['mmm',]:
1473            if iA in [0,1,2]:
1474                parm = '%d::A%s'%(pId,iA)
1475            else:
1476                parm = None
1477        elif SGLaue == '2/m':
1478            if iA in [0,1,2]:
1479                parm = '%d::A%s'%(pId,iA)
1480            elif iA == 3 and SGUniq == 'c':
1481                parm = '%d::A%s'%(pId,iA)
1482            elif iA == 4 and SGUniq == 'b':
1483                parm = '%d::A%s'%(pId,iA)
1484            elif iA == 5 and SGUniq == 'a':
1485                parm = '%d::A%s'%(pId,iA)
1486            else:
1487                parm = None           
1488        else:
1489            parm = '%d::A%s'%(pId,iA)
1490        return parm
1491   
1492    Histograms,Phases = G2frame.GetUsedHistogramsAndPhasesfromTree()
1493    UseList = newPhase['Histograms']
1494    detTrans = np.abs(nl.det(Trans))
1495    invTrans = nl.inv(Trans)
1496    opId = oldPhase['pId']
1497    npId = newPhase['pId']
1498    cx,ct,cs,cia = newPhase['General']['AtomPtrs']
1499    nAtoms = newPhase['Atoms']
1500    oSGData = oldPhase['General']['SGData']
1501    nSGData = newPhase['General']['SGData']
1502    #oAcof = G2lat.cell2A(oldPhase['General']['Cell'][1:7])
1503    #nAcof = G2lat.cell2A(newPhase['General']['Cell'][1:7])
1504    item = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'Constraints')
1505    if not item:
1506        print('Error: no constraints in Data Tree')
1507        return
1508    constraints = G2frame.GPXtree.GetItemPyData(item)
1509    xnames = ['dAx','dAy','dAz']
1510    # constraints on matching atom params between phases
1511    for ia,code in enumerate(atCodes):
1512        atom = nAtoms[ia]
1513        if not ia and atom[cia] == 'A':
1514            wx.MessageDialog(G2frame,
1515                'Anisotropic thermal motion constraints are not developed at the present time',
1516                'Anisotropic thermal constraint?',style=wx.ICON_INFORMATION).ShowModal()
1517        siteSym = G2spc.SytSym(atom[cx:cx+3],nSGData)[0]
1518        CSX = G2spc.GetCSxinel(siteSym)
1519#        CSU = G2spc.GetCSuinel(siteSym)
1520        item = code.split('+')[0]
1521        iat,opr = item.split(':')
1522        Nop = abs(int(opr))%100-1
1523        if '-' in opr:
1524            Nop *= -1
1525        Opr = oldPhase['General']['SGData']['SGOps'][abs(Nop)][0]
1526        if Nop < 0:         #inversion
1527            Opr *= -1
1528        XOpr = np.inner(Opr,Trans)
1529        for i,ix in enumerate(list(CSX[0])):
1530            if not ix:
1531                continue
1532            name = xnames[i]
1533            IndpCon = [1.0,G2obj.G2VarObj('%d::%s:%d'%(npId,name,ia))]
1534            DepCons = []
1535            for iop,opval in enumerate(XOpr[i]):
1536                if opval:
1537                    DepCons.append([opval,G2obj.G2VarObj('%d::%s:%s'%(opId,xnames[iop],iat))])
1538            if len(DepCons) == 1:
1539                constraints['Phase'].append([DepCons[0],IndpCon,None,None,'e'])
1540            elif len(DepCons) > 1:
1541                for Dep in DepCons:
1542                    Dep[0] *= -1
1543                constraints['Phase'].append([IndpCon]+DepCons+[0.0,None,'c'])
1544        for name in ['Afrac','AUiso']:
1545            IndpCon = [1.0,G2obj.G2VarObj('%d::%s:%d'%(npId,name,ia))]
1546            DepCons = [1.0,G2obj.G2VarObj('%d::%s:%s'%(opId,name,iat))]
1547            constraints['Phase'].append([DepCons,IndpCon,None,None,'e'])
1548           
1549        # unfinished Anisotropic constraint generation
1550#        Uids = [[0,0,'AU11'],[1,1,'AU22'],[2,2,'AU33'],[0,1,'AU12'],[0,2,'AU13'],[1,2,'AU23']]
1551#        DepConsDict = dict(zip(Us,[[],[],[],[],[],[]]))
1552#        for iu,Uid in enumerate(Uids):
1553#            UMT = np.zeros((3,3))
1554#            UMT[Uid[0],Uid[1]] = 1
1555#            nUMT = G2lat.prodMGMT(UMT,invTrans)
1556#            nUT = G2lat.UijtoU6(nUMT)
1557#            for iu,nU in enumerate(nUT):
1558#                if abs(nU) > 1.e-8:
1559#                    parm = '%d::%s;%s'%(opId,Us[iu],iat)
1560#                    DepConsDict[Uid[2]].append([abs(nU%1.),G2obj.G2VarObj(parm)])
1561#        nUcof = atom[iu:iu+6]
1562#        conStrings = []
1563#        for iU,Usi in enumerate(Us):
1564#            parm = '%d::%s;%d'%(npId,Usi,ia)
1565#            parmDict[parm] = nUcof[iU]
1566#            varyList.append(parm)
1567#            IndpCon = [1.0,G2obj.G2VarObj(parm)]
1568#            conStr = str([IndpCon,DepConsDict[Usi]])
1569#            if conStr in conStrings:
1570#                continue
1571#            conStrings.append(conStr)
1572#            if len(DepConsDict[Usi]) == 1:
1573#                if DepConsDict[Usi][0]:
1574#                    constraints['Phase'].append([IndpCon,DepConsDict[Usi][0],None,None,'e'])
1575#            elif len(DepConsDict[Usi]) > 1:       
1576#                for Dep in DepConsDict[Usi]:
1577#                    Dep[0] *= -1
1578#                constraints['Phase'].append([IndpCon]+DepConsDict[Usi]+[0.0,None,'c'])
1579           
1580        #how do I do Uij's for most Trans?
1581
1582    # constraints on lattice parameters between phases
1583#    T = nl.inv(Trans).T
1584    T = Trans.T
1585    conMat = [
1586        [T[0,0]**2,T[0,1]**2,T[0,2]**2,T[0,0]*T[0,1],T[0,0]*T[0,2],T[0,1]*T[0,2]],
1587        [T[1,0]**2,T[1,1]**2,T[1,2]**2,T[1,0]*T[1,1],T[1,0]*T[1,2],T[1,1]*T[1,2]],
1588        [T[2,0]**2,T[2,1]**2,T[2,2]**2,T[2,0]*T[2,1],T[2,0]*T[2,2],T[2,1]*T[2,2]],
1589        [2.*T[0,0]*T[1,0],2.*T[0,1]*T[1,1],2.*T[0,2]*T[1,2],T[0,0]*T[1,1]+T[0,1]*T[1,0],T[0,0]*T[1,2]+T[0,2]*T[1,0],T[0,1]*T[1,2]+T[0,2]*T[1,1]],
1590        [2.*T[0,0]*T[2,0],2.*T[0,1]*T[2,1],2.*T[0,2]*T[2,2],T[0,0]*T[2,1]+T[0,1]*T[2,0],T[0,0]*T[2,2]+T[0,2]*T[2,0],T[0,1]*T[2,2]+T[0,2]*T[2,1]],
1591        [2.*T[1,0]*T[2,0],2.*T[1,1]*T[2,1],2.*T[1,2]*T[2,2],T[1,0]*T[2,1]+T[1,1]*T[2,0],T[1,0]*T[2,2]+T[1,2]*T[2,0],T[1,1]*T[2,2]+T[1,2]*T[2,1]]
1592        ]
1593    # Gnew = conMat * A:
1594#         T00**2*a0  T01**2*a1 T02**2*a2 T00*T01*a3    T00*T02*a4    T01*T02*a5
1595#         T10**2*a0  T11**2*a1 T12**2*a2 T10*T11*a3    T10*T12*a4    T11*T12*a5
1596#         T20**2*a0  T21**2*a1 T22**2*a2 T20*T21*a3    T20*T22*a4    T21*T22*a5
1597#         2*T00*T10*a0      2*T01*T11*a1     2*T02*T12*a2     (T00*T11 + T01*T10)*a3      (T00*T12 + T02*T10)*a4      (T01*T12 + T02*T11)*a5
1598#         2*T00*T20*a0      2*T01*T21*a1     2*T02*T22*a2     (T00*T21 + T01*T20)*a3      (T00*T22 + T02*T20)*a4      (T01*T22 + T02*T21)*a5
1599#         2*T10*T20*a0      2*T11*T21*a1     2*T12*T22*a2     (T10*T21 + T11*T20)*a3      (T10*T22 + T12*T20)*a4      (T11*T22 + T12*T21)*a5
1600    # Generated as symbolic code using:
1601    # import sym
1602    # A0, A1, A2, A3, A4, A5 = sym.symbols('A0, A1, A2, A3, A4, A5')
1603    # G = sym.Matrix([ [A0,    A3/2.,  A4/2.], [A3/2.,  A1,    A5/2.], [A4/2., A5/2.,    A2]])
1604    # transformation matrix
1605    # T00, T10, T20, T01, T11, T21, T02, T12, T22 = sym.symbols('T00, T10, T20, T01, T11, T21, T02, T12, T22')
1606    # Tr = sym.Matrix([ [T00, T10, T20], [T01, T11, T21], [T02, T12, T22],])
1607    # Gnew = (Tr.T*G)*Tr
1608   
1609    #print('old A',G2lat.cell2A(oldPhase['General']['Cell'][1:7]))
1610    #print('new A',G2lat.cell2A(newPhase['General']['Cell'][1:7]))
1611   
1612#this is still incorrect for hex/trig/ortho/tetragonal --> monoclinic
1613   
1614#    for iAnew,Asi in enumerate(['A0','A1','A2','A3','A4','A5']): # loop through A[i] for new cell
1615#        Nparm = str(npId) + '::' + Asi
1616#        if Nparm != SetUniqAj(npId,iAnew,nSGData):
1617#            continue # skip: Ai constrained from Aj or must be zero
1618#        multDict = {}
1619#        for iAorg in range(6):
1620#            cA = conMat[iAnew][iAorg] # coeff for A[i] in constraint matrix
1621#            if abs(cA) < 1.e-8: continue
1622#            parm = SetUniqAj(opId,iAorg,oSGData) # translate to unique A[i] in original cell
1623#            if not parm: continue # must be zero
1624#            # sum coeff
1625#            if parm in multDict:
1626#                multDict[parm] += cA
1627#            else:
1628#                multDict[parm] = cA
1629#        # any non-zero multipliers?
1630#        maxMult = 0
1631#        for i in multDict:
1632#            maxMult = max(maxMult,abs(multDict[i]))
1633#        if maxMult <= 0:  # Nparm computes as zero; Fix this parameter
1634#            constraints['Phase'] += [[
1635#                [0.0,G2obj.G2VarObj(Nparm)],
1636#                None,None,'h']]
1637#        elif len(multDict) == 1:        # create equivalence
1638#            key = list(multDict.keys())[0]
1639#            constraints['Phase'] += [[
1640#                [1.0,G2obj.G2VarObj(key)],
1641#                [multDict[key],G2obj.G2VarObj(Nparm)],
1642#                None,None,'e']]
1643#        else:                           # create constraint
1644#            constr = [[-1.0,G2obj.G2VarObj(Nparm)]]
1645#            for key in multDict:
1646#                constr += [[multDict[key],G2obj.G2VarObj(key)]]
1647#            constr += [0.0,None,'c']
1648#            constraints['Phase'] += [constr]
1649   
1650    # constraints on HAP Scale, etc.
1651    for hId,hist in enumerate(UseList):    #HAP - seems OK
1652        ohapkey = '%d:%d:'%(opId,hId)
1653        nhapkey = '%d:%d:'%(npId,hId)
1654        IndpCon = [1.0,G2obj.G2VarObj(ohapkey+'Scale')]
1655        DepCons = [detTrans,G2obj.G2VarObj(nhapkey+'Scale')]
1656        constraints['HAP'].append([DepCons,IndpCon,None,None,'e'])
1657        for name in ['Size;i','Mustrain;i']:
1658            IndpCon = [1.0,G2obj.G2VarObj(ohapkey+name)]
1659            DepCons = [1.0,G2obj.G2VarObj(nhapkey+name)]
1660            constraints['HAP'].append([IndpCon,DepCons,None,None,'e'])
1661       
1662################################################################################
1663#### Rigid bodies
1664################################################################################
1665resRBsel = None
1666def UpdateRigidBodies(G2frame,data):
1667    '''Called when Rigid bodies tree item is selected.
1668    Displays the rigid bodies in the data window
1669    '''
1670    if not data.get('RBIds') or not data:
1671        data.update({'Vector':{'AtInfo':{}},'Residue':{'AtInfo':{}},
1672            'RBIds':{'Vector':[],'Residue':[]}})       #empty/bad dict - fill it
1673           
1674    global resList,resRBsel
1675    Indx = {}
1676    resList = []
1677    plotDefaults = {'oldxy':[0.,0.],'Quaternion':[0.,0.,0.,1.],'cameraPos':30.,'viewDir':[0,0,1],}
1678
1679    G2frame.rbBook = G2G.GSNoteBook(parent=G2frame.dataWindow)
1680    G2frame.dataWindow.GetSizer().Add(G2frame.rbBook,1,wx.ALL|wx.EXPAND)
1681    VectorRB = wx.ScrolledWindow(G2frame.rbBook)
1682    VectorRBDisplay = wx.Panel(VectorRB)
1683    G2frame.rbBook.AddPage(VectorRB,'Vector rigid bodies')
1684    ResidueRB = wx.ScrolledWindow(G2frame.rbBook)
1685    ResidueRBDisplay = wx.Panel(ResidueRB)
1686    G2frame.rbBook.AddPage(ResidueRB,'Residue rigid bodies')
1687   
1688    def OnPageChanged(event):
1689        global resList
1690        resList = []
1691        if event:       #page change event!
1692            page = event.GetSelection()
1693        else:
1694            page = G2frame.rbBook.GetSelection()
1695        G2frame.rbBook.ChangeSelection(page)
1696        text = G2frame.rbBook.GetPageText(page)
1697        if text == 'Vector rigid bodies':
1698            G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.VectorBodyMenu)
1699            G2frame.Bind(wx.EVT_MENU, AddVectorRB, id=G2G.wxID_VECTORBODYADD)
1700            G2frame.Bind(wx.EVT_MENU, ExtractPhaseRB, id=G2G.wxID_VECTORBODYIMP)
1701            G2frame.Bind(wx.EVT_MENU, AddVectTrans, id=G2G.wxID_VECTORBODYEXTD)
1702            G2frame.Bind(wx.EVT_MENU, SaveVectorRB, id=G2G.wxID_VECTORBODYSAV)
1703            G2frame.Bind(wx.EVT_MENU, ReadVectorRB, id=G2G.wxID_VECTORBODYRD)
1704            G2frame.Page = [page,'vrb']
1705            UpdateVectorRB()
1706        elif text == 'Residue rigid bodies':
1707            G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.RigidBodyMenu)
1708            G2frame.Bind(wx.EVT_MENU, AddResidueRB, id=G2G.wxID_RIGIDBODYADD)
1709            G2frame.Bind(wx.EVT_MENU, ExtractPhaseRB, id=G2G.wxID_RIGIDBODYIMP)
1710            G2frame.Bind(wx.EVT_MENU, OnImportRigidBody, id=G2G.wxID_RIGIDBODYIMPORT)
1711            G2frame.Bind(wx.EVT_MENU, OnSaveRigidBody, id=G2G.wxID_RIGIDBODYSAVE)
1712            G2frame.Bind(wx.EVT_MENU, OnDefineTorsSeq, id=G2G.wxID_RESIDUETORSSEQ) #enable only if residue RBs exist?
1713            G2frame.Bind(wx.EVT_MENU, DumpVectorRB, id=G2G.wxID_RESBODYSAV)
1714            G2frame.Bind(wx.EVT_MENU, LoadVectorRB, id=G2G.wxID_RESBODYRD)
1715            G2frame.Page = [page,'rrb']
1716            UpdateResidueRB()
1717        else:
1718            G2gd.SetDataMenuBar(G2frame)
1719            #G2frame.Page = [page,'rrb']
1720           
1721    def getMacroFile(macName):
1722        defDir = os.path.join(os.path.split(__file__)[0],'GSASIImacros')
1723        dlg = wx.FileDialog(G2frame,message='Choose '+macName+' rigid body macro file',
1724            defaultDir=defDir,defaultFile="",wildcard="GSAS-II macro file (*.mac)|*.mac",
1725            style=wx.FD_OPEN | wx.FD_CHANGE_DIR)
1726        try:
1727            if dlg.ShowModal() == wx.ID_OK:
1728                macfile = dlg.GetPath()
1729                macro = open(macfile,'Ur')
1730                head = macro.readline()
1731                if macName not in head:
1732                    print (head)
1733                    print ('**** ERROR - wrong restraint macro file selected, try again ****')
1734                    macro = []
1735            else: # cancel was pressed
1736                macro = []
1737        finally:
1738            dlg.Destroy()
1739        return macro        #advanced past 1st line
1740       
1741    def getTextFile():
1742        dlg = wx.FileDialog(G2frame,'Choose rigid body text file', G2frame.LastGPXdir, '',
1743            "GSAS-II text file (*.txt)|*.txt|XYZ file (*.xyz)|*.xyz|"
1744            "Sybyl mol2 file (*.mol2)|*.mol2|PDB file (*.pdb;*.ent)|*.pdb;*.ent",
1745            wx.FD_OPEN | wx.FD_CHANGE_DIR)
1746        try:
1747            if dlg.ShowModal() == wx.ID_OK:
1748                txtfile = dlg.GetPath()
1749                ext = os.path.splitext(txtfile)[1]
1750                text = open(txtfile,'Ur')
1751            else: # cancel was pressed
1752                ext = ''
1753                text = []
1754        finally:
1755            dlg.Destroy()
1756        if 'ent' in ext:
1757            ext = '.pdb'
1758        return text,ext.lower()
1759       
1760    def OnImportRigidBody(event):
1761        page = G2frame.rbBook.GetSelection()
1762        if 'Vector' in G2frame.rbBook.GetPageText(page):
1763            pass
1764        elif 'Residue' in G2frame.rbBook.GetPageText(page):
1765            ImportResidueRB()
1766           
1767    def OnSaveRigidBody(event):
1768        page = G2frame.rbBook.GetSelection()
1769        if 'Vector' in G2frame.rbBook.GetPageText(page):
1770            pass
1771        elif 'Residue' in G2frame.rbBook.GetPageText(page):
1772            SaveResidueRB()
1773           
1774    def DumpVectorRB(event):
1775        global resRBsel
1776        if resRBsel not in data['Residue']:
1777            return
1778        rbData = data['Residue'][resRBsel]
1779        pth = G2G.GetExportPath(G2frame)
1780        dlg = wx.FileDialog(G2frame, 'Choose file to save residue rigid body',
1781            pth, '', 'RRB files (*.resbody)|*.resbody',
1782            wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)
1783        try:
1784            if dlg.ShowModal() == wx.ID_OK:
1785                filename = dlg.GetPath()
1786                filename = os.path.splitext(filename)[0]+'.resbody'  # set extension
1787                fp = open(filename,'w')
1788                fp.write('Name: '+rbData['RBname']+'\n')
1789                fp.write('atNames: ')
1790                for i in rbData['atNames']:
1791                    fp.write(str(i)+" ") 
1792                fp.write('\n')
1793                for item in rbData['rbSeq']:
1794                    fp.write('rbSeq: ') 
1795                    fp.write('{:d} {:d} {:.1f}: '.format(*item[:3]))
1796                    for num in item[3]:
1797                        fp.write('{:d} '.format(num))
1798                    fp.write('\n')
1799                for i,sym in enumerate(rbData['rbTypes']):
1800                    fp.write("{:3s}".format(sym))
1801                    fp.write('{:8.5f}{:9.5f}{:9.5f}   '
1802                            .format(*rbData['rbXYZ'][i]))
1803                    fp.write('\n')
1804                fp.close()
1805                print ('Vector rigid body saved to: '+filename)
1806               
1807        finally:
1808            dlg.Destroy()
1809       
1810    def LoadVectorRB(event):
1811        AtInfo = data['Residue']['AtInfo']
1812        pth = G2G.GetExportPath(G2frame)
1813        dlg = wx.FileDialog(G2frame, 'Choose file to read vector rigid body',
1814            pth, '', 'RRB files (*.resbody)|*.resbody',
1815            wx.FD_OPEN)
1816        try:
1817            if dlg.ShowModal() == wx.ID_OK:
1818                filename = dlg.GetPath()
1819                filename = os.path.splitext(filename)[0]+'.resbody'  # set extension
1820                fp = open(filename,'r')
1821                l = fp.readline().strip()
1822                if 'Name' not in l:
1823                    fp.close()
1824                    G2frame.ErrorDialog('Read Error',
1825                        'File '+filename+' does not start with Name\nFirst line ='
1826                        +l+'\ninvalid file',parent=G2frame)
1827                    return
1828                name = l.split(':')[1].strip()
1829                line = fp.readline().strip().split(':')[1].split()
1830                atNames = [i for i in line]
1831                types = []
1832                coords = []
1833                l = fp.readline().strip()
1834                rbSeq = []
1835                while 'rbSeq' in l:
1836                    tag,vals,lst = l.split(':')
1837                    seq = []
1838                    for t,v in zip((int,int,float),vals.split()): 
1839                        seq.append(t(v))
1840                    seq.append([])
1841                    for num in lst.split():
1842                        seq[-1].append(int(num))
1843                    rbSeq.append(seq)
1844                    l = fp.readline().strip()                   
1845                while l:
1846                    nums = l.strip().split()
1847                    types.append(nums.pop(0))
1848                    t = types[-1]
1849                    if t not in AtInfo:
1850                        Info = G2elem.GetAtomInfo(t)
1851                        AtInfo[t] = [Info['Drad'],Info['Color']]
1852                    coords.append([float(nums.pop(0)) for j in range(3)])
1853                    l = fp.readline().strip()
1854                fp.close()
1855            else:
1856                return       
1857        finally:
1858            dlg.Destroy()
1859        coords = np.array(coords)
1860        rbid = ran.randint(0,sys.maxsize)
1861        data['Residue'][rbid] = {'RBname':name,
1862                'rbXYZ': coords,
1863                'rbRef':[0,1,2,False],
1864                'rbTypes':types, 'atNames':atNames,
1865                'useCount':0,
1866                'rbSeq':rbSeq, 'SelSeq':[0,0],}
1867        data['RBIds']['Residue'].append(rbid)
1868        UpdateResidueRB()
1869   
1870    def AddVectorRB(event):
1871        'Create a new vector rigid body'
1872        AtInfo = data['Vector']['AtInfo']
1873        dlg = G2G.MultiIntegerDialog(G2frame,'New Rigid Body',['No. atoms','No. translations'],[1,1])
1874        if dlg.ShowModal() == wx.ID_OK:
1875            nAtoms,nTrans = dlg.GetValues()
1876            rbid = ran.randint(0,sys.maxsize)
1877            vecMag = [1.0 for i in range(nTrans)]
1878            vecRef = [False for i in range(nTrans)]
1879            vecVal = [np.zeros((nAtoms,3)) for j in range(nTrans)]
1880            rbTypes = ['C' for i in range(nAtoms)]
1881            Info = G2elem.GetAtomInfo('C')
1882            AtInfo['C'] = [Info['Drad'],Info['Color']]
1883            data['Vector'][rbid] = {'RBname':'UNKRB','VectMag':vecMag,'rbXYZ':np.zeros((nAtoms,3)),
1884                'rbRef':[0,1,2,False],'VectRef':vecRef,'rbTypes':rbTypes,'rbVect':vecVal,'useCount':0}
1885            data['RBIds']['Vector'].append(rbid)
1886        dlg.Destroy()
1887        UpdateVectorRB()
1888
1889    def ExtractPhaseRB(event):
1890        'Extract a rigid body from a file with a phase'
1891        def SetupDrawing(atmData):
1892            '''Add the dicts needed for G2plt.PlotStructure to work to the
1893            reader .Phase object
1894            '''
1895            generalData = atmData['General']
1896            generalData['BondRadii'] = []
1897
1898            G2phG.SetDrawingDefaults(atmData['Drawing'])
1899            atmData['Drawing'].update(
1900                {'oldxy':[0.,0.],'Quaternion':[0.,0.,0.,1.],'cameraPos':150.,
1901                     'viewDir':[0,0,1],'atomPtrs': [2, 1, 6, 17],
1902                     })
1903            atmData['Drawing']['showRigidBodies'] = False
1904            generalData['Map'] = {'MapType':False, 'rho':[]}
1905            generalData['AtomTypes'] = []
1906            generalData['BondRadii'] = []
1907            generalData['AngleRadii'] = []
1908            generalData['vdWRadii'] = []
1909            generalData['Color'] = []
1910            generalData['Isotopes'] = {}
1911            generalData['Isotope'] = {}
1912            cx,ct,cs,cia = generalData['AtomPtrs']
1913            generalData['Mydir'] = G2frame.dirname
1914            for iat,atom in enumerate(atmData['Atoms']):
1915                atom[ct] = atom[ct].lower().capitalize()      #force elem symbol to standard form
1916                if atom[ct] not in generalData['AtomTypes'] and atom[ct] != 'UNK':
1917                    Info = G2elem.GetAtomInfo(atom[ct])
1918                    if not Info:
1919                        atom[ct] = 'UNK'
1920                        continue
1921                    atom[ct] = Info['Symbol'] # N.B. symbol might be changed by GetAtomInfo
1922                    generalData['AtomTypes'].append(atom[ct])
1923                    generalData['Z'] = Info['Z']
1924                    generalData['Isotopes'][atom[ct]] = Info['Isotopes']
1925                    generalData['BondRadii'].append(Info['Drad'])
1926                    generalData['AngleRadii'].append(Info['Arad'])
1927                    generalData['vdWRadii'].append(Info['Vdrad'])
1928                    if atom[ct] in generalData['Isotope']:
1929                        if generalData['Isotope'][atom[ct]] not in generalData['Isotopes'][atom[ct]]:
1930                            isotope = list(generalData['Isotopes'][atom[ct]].keys())[-1]
1931                            generalData['Isotope'][atom[ct]] = isotope
1932                    else:
1933                        generalData['Isotope'][atom[ct]] = 'Nat. Abund.'
1934                        if 'Nat. Abund.' not in generalData['Isotopes'][atom[ct]]:
1935                            isotope = list(generalData['Isotopes'][atom[ct]].keys())[-1]
1936                            generalData['Isotope'][atom[ct]] = isotope
1937                    generalData['Color'].append(Info['Color'])
1938                    if generalData['Type'] == 'magnetic':
1939                        if len(landeg) < len(generalData['AtomTypes']):
1940                            landeg.append(2.0)
1941            atmData['Drawing']['Atoms'] = []
1942            for atom in atmData['Atoms']:
1943                atmData['Drawing']['Atoms'].append(MakeDrawAtom(atmData,atom))
1944
1945        def onCancel(event,page=0):
1946            'complete or bail out from RB define, cleaning up'
1947            G2frame.rbBook.DeletePage(G2frame.rbBook.FindPage(pagename))
1948            G2frame.rbBook.SetSelection(page)
1949
1950        def Page1():
1951            '''Show the GUI for first stage of the rigid body with all atoms in
1952            phase in crystal coordinates. Select the atoms to go onto the
1953            next stage
1954            '''
1955            def ShowSelection(selections):
1956                'respond to change in atom selections'
1957                ct,cs = [1,8]
1958                generalData = rd.Phase['General']
1959                for i,atom in enumerate(rd.Phase['Drawing']['Atoms']):
1960                    if i in selections:
1961                        factor = 1
1962                    else:
1963                        factor = 2.5
1964                    atNum = generalData['AtomTypes'].index(atom[ct]) 
1965                    atom[cs] = list(np.array(generalData['Color'][atNum])//factor) 
1966                draw(*drawArgs)
1967            def onPage1OK(event):
1968                '1st section has been completed, move onto next'
1969                G2frame.G2plotNB.Delete(rd.Phase['General']['Name'])
1970                GetCoords(atmsel)
1971                Page2()
1972
1973            SetupDrawing(rd.Phase) # add information to reader object to allow plotting
1974            atomlist = [atom[0] for atom in rd.Phase['Atoms']]
1975            atmsel = list(range(len(rd.Phase['Atoms'])))
1976            # broken -- # why no bonds?
1977            #for atm in rd.Phase['Drawing']['Atoms']:
1978            #    atm[6] = 'balls & sticks'
1979
1980            draw,drawArgs = G2plt.PlotStructure(G2frame,rd.Phase,True)
1981            ShowSelection(atmsel)
1982
1983            if G2frame.rbBook.FindPage(pagename) is not None:
1984                G2frame.rbBook.DeletePage(G2frame.rbBook.FindPage(pagename))
1985
1986            RBImp = wx.ScrolledWindow(G2frame.rbBook)
1987            RBImpPnl = wx.Panel(RBImp)
1988            G2frame.rbBook.AddPage(RBImp,pagename)
1989            G2frame.rbBook.SetSelection(G2frame.rbBook.FindPage(pagename))
1990
1991            mainSizer = G2G.G2MultiChoiceWindow(RBImpPnl,
1992                            'Select atoms to import',
1993                            atomlist,atmsel,OnChange=ShowSelection)
1994
1995            # OK/Cancel buttons       
1996            btnsizer = wx.StdDialogButtonSizer()
1997            OKbtn = wx.Button(RBImpPnl, wx.ID_OK, 'Continue')
1998            OKbtn.SetDefault()
1999            btnsizer.AddButton(OKbtn)
2000            OKbtn.Bind(wx.EVT_BUTTON,onPage1OK)
2001            btn = wx.Button(RBImpPnl, wx.ID_CANCEL)
2002            btn.Bind(wx.EVT_BUTTON,onCancel)
2003            btnsizer.AddButton(btn)
2004            btnsizer.Realize()
2005            mainSizer.Add(btnsizer,0,wx.ALIGN_CENTER,50)
2006
2007            RBImpPnl.SetSizer(mainSizer,True)
2008
2009            mainSizer.Layout()   
2010            Size = mainSizer.GetMinSize()
2011            Size[0] += 40
2012            Size[1] = max(Size[1],G2frame.GetSize()[1]-200) + 20
2013            RBImpPnl.SetSize(Size)
2014            RBImp.SetScrollbars(10,10,Size[0]/10-4,Size[1]/10-1)
2015            RBImp.Scroll(0,0)
2016
2017        def Page2():
2018            '''Show the GUI for the second stage, where selected atoms are
2019            now in Cartesian space, manipulate the axes and export selected
2020            atoms to a vector or residue rigid body.
2021            '''
2022            def UpdateDraw(event=None):
2023                'Called when info changes in grid, replots'
2024                UpdateVectorBody(rbData)
2025                DrawCallback()
2026               
2027            def onSetAll(event):
2028                'Set all atoms as selected'
2029                for i in range(len(rd.Phase['RBselection'])):
2030                    rd.Phase['RBselection'][i] = True
2031                grid.ForceRefresh()
2032                UpdateDraw()
2033               
2034            def onToggle(event):
2035                'Toggles selection state for all atoms'
2036                grid.completeEdits()
2037                for i in range(len(rd.Phase['RBselection'])):
2038                    rd.Phase['RBselection'][i] = not rd.Phase['RBselection'][i]
2039                grid.ForceRefresh()
2040                UpdateDraw()
2041               
2042            def onSetOrigin(event):
2043                'Resets origin to midpoint between all selected atoms'
2044                grid.completeEdits()
2045                center = np.array([0.,0.,0.])
2046                count = 0
2047                for i in range(len(rd.Phase['RBselection'])):
2048                    if rd.Phase['RBselection'][i]:
2049                        count += 1
2050                        center += rd.Phase['RBcoords'][i]
2051                if count:
2052                    rd.Phase['RBcoords'] -= center/count
2053                grid.ForceRefresh()
2054                UpdateDraw()
2055               
2056            def onSetX(event):
2057                grid.completeEdits()
2058                center = np.array([0.,0.,0.])
2059                count = 0
2060                for i in range(len(rd.Phase['RBselection'])):
2061                    if rd.Phase['RBselection'][i]:
2062                        count += 1
2063                        center += rd.Phase['RBcoords'][i]
2064                if not count:
2065                    return
2066                XP = center/count
2067                if np.sqrt(sum(XP**2)) < 0.1:
2068                    G2G.G2MessageBox(G2frame,
2069                            'The selected atom(s) are too close to the origin',
2070                            'near origin')
2071                    return
2072                XP /= np.sqrt(np.sum(XP**2))
2073                Z = np.array((0,0,1.))
2074                YP = np.cross(Z,XP)
2075                ZP = np.cross(XP,YP)
2076                trans = np.array((XP,YP,ZP))
2077                # update atoms in place
2078                rd.Phase['RBcoords'][:] = np.inner(trans,rd.Phase['RBcoords']).T
2079                grid.ForceRefresh()
2080                UpdateDraw()
2081
2082            def onSetPlane(event):
2083                '''Compute least-squares plane for selected atoms;
2084                move atoms so that LS plane aligned with x-y plane,
2085                with minimum required change to x
2086                '''
2087                grid.completeEdits()
2088                #X,Y,Z = rd.Phase['RBcoords'][rd.Phase['RBselection']].T
2089                XYZ = rd.Phase['RBcoords'][rd.Phase['RBselection']]
2090                Z = copy.copy(XYZ[:,2])
2091                if len(Z) < 3: 
2092                    G2G.G2MessageBox(G2frame,'A plane requires three or more atoms',
2093                                     'Need more atoms')
2094                    return
2095                XY0 = copy.copy(XYZ)
2096                XY0[:,2] = 1
2097                # solve for  ax + bx + z + c = 0 or equivalently ax + bx + c = -z
2098                try:
2099                    (a,b,c), resd, rank, sing = nl.lstsq(XY0, -Z)
2100                except: 
2101                    G2G.G2MessageBox(G2frame,
2102                            'Error computing plane; are atoms in a line?',
2103                            'Computation error')
2104                    return
2105                # new coordinate system is z' (zp, normal to plane = [a,b,1]),
2106                # y' = z' cross x (YP, = [0,1,-b])
2107                # x' = (z' cross x) cross z'
2108                # this puts XP as close as possible to X with XP & YP in plane
2109                ZP = np.array([a,b,1])
2110                ZP /= np.sqrt(np.sum(ZP**2))
2111                YP = np.array([0,1,-b])
2112                YP /= np.sqrt(np.sum(YP**2))
2113                XP = np.cross(YP,ZP)
2114                trans = np.array((XP,YP,ZP))
2115                # update atoms in place
2116                rd.Phase['RBcoords'][:] = np.inner(trans,rd.Phase['RBcoords']).T
2117                grid.ForceRefresh()
2118                UpdateDraw()
2119
2120            def onAddVector(event):
2121                '''Adds selected atoms as a new vector rigid body.
2122                Closes out the importer tab when done.
2123                '''
2124                grid.completeEdits()
2125                rb = MakeVectorBody(os.path.split(filename)[1])
2126                UpdateVectorBody(rb,True)
2127                if len(rb['rbTypes']) < 3: return # must have at least 3 atoms
2128                rbid = ran.randint(0,sys.maxsize)
2129                data['Vector'][rbid] = rb
2130                data['RBIds']['Vector'].append(rbid)
2131                AtInfo = data['Vector']['AtInfo']
2132                for t in rb['rbTypes']:
2133                    if t in data['Vector']['AtInfo']: continue
2134                    Info = G2elem.GetAtomInfo(t)
2135                    data['Vector']['AtInfo'][t] = [Info['Drad'],Info['Color']]
2136                G2frame.G2plotNB.Delete('Rigid body')
2137                onCancel(event,0)
2138               
2139            def onAddResidue(event):
2140                '''Adds selected atoms as a new residue rigid body.
2141                Closes out the importer tab when done.
2142                '''
2143                grid.completeEdits()
2144                name = os.path.split(filename)[1]
2145                rbXYZ = []
2146                rbTypes = []
2147                atNames = []
2148                for i in rd.Phase['RBindex']:
2149                    if rd.Phase['RBselection'][i]:
2150                        rbXYZ.append(rd.Phase['RBcoords'][i])
2151                        rbTypes.append(rd.Phase['RBtypes'][i])
2152                        atNames.append(rd.Phase['RBlbls'][i])
2153                if len(rbTypes) < 3: return # must have at least 3 atoms
2154                rbXYZ = np.array(rbXYZ)
2155                rbid = ran.randint(0,sys.maxsize)
2156                data['Residue'][rbid] = {'RBname':name,'rbXYZ':rbXYZ,
2157                    'rbTypes':rbTypes,'atNames':atNames,'rbRef':[0,1,2,False],
2158                    'rbSeq':[],'SelSeq':[0,0],'useCount':0}
2159                data['RBIds']['Residue'].append(rbid)
2160                AtInfo = data['Residue']['AtInfo']
2161                for t in rbTypes:
2162                    if t in data['Residue']['AtInfo']: continue
2163                    Info = G2elem.GetAtomInfo(t)
2164                    data['Residue']['AtInfo'][t] = [Info['Drad'],Info['Color']]
2165
2166                print ('Rigid body added')
2167                G2frame.G2plotNB.Delete('Rigid body')
2168                onCancel(event,1)
2169
2170            if G2frame.rbBook.FindPage(pagename) is not None:
2171                G2frame.rbBook.DeletePage(G2frame.rbBook.FindPage(pagename))
2172            RBImp = wx.ScrolledWindow(G2frame.rbBook)
2173            RBImpPnl = wx.Panel(RBImp)
2174            G2frame.rbBook.AddPage(RBImp,pagename)
2175            G2frame.rbBook.SetSelection(G2frame.rbBook.FindPage(pagename))
2176            generalData = rd.Phase['General']
2177            AtInfo = {}
2178            ct = 1
2179            for t in rd.Phase['RBtypes']:
2180                if t in AtInfo: continue
2181                Info = G2elem.GetAtomInfo(t)
2182                AtInfo[t] = [Info['Drad'],Info['Color']]
2183            plotDefaults = {'oldxy':[0.,0.],'Quaternion':[0.,0.,0.,1.],'cameraPos':30.,'viewDir':[0,0,1],}
2184
2185            rd.Phase['RBindex'] = list(range(len(rd.Phase['RBtypes'])))
2186            rd.Phase['RBselection'] = len(rd.Phase['RBtypes']) * [True]
2187            rbData = MakeVectorBody()
2188            DrawCallback = G2plt.PlotRigidBody(G2frame,'Vector',
2189                                    AtInfo,rbData,plotDefaults)
2190
2191            mainSizer = wx.BoxSizer(wx.HORIZONTAL)
2192            gridSizer = wx.BoxSizer(wx.VERTICAL)
2193            grid = DragableRBGrid(RBImpPnl,rd.Phase,UpdateDraw)
2194            gridSizer.Add(grid)
2195            gridSizer.Add(
2196                wx.StaticText(RBImpPnl,wx.ID_ANY,'Reorder atoms by dragging'),
2197                0,wx.ALL)
2198            mainSizer.Add(gridSizer)
2199            mainSizer.Add((5,5))
2200            btnSizer = wx.BoxSizer(wx.VERTICAL)
2201            btn = wx.Button(RBImpPnl, wx.ID_OK, 'Set All')
2202            btn.Bind(wx.EVT_BUTTON,onSetAll)
2203            btnSizer.Add(btn,0,wx.ALIGN_CENTER)
2204            btn = wx.Button(RBImpPnl, wx.ID_OK, 'Toggle')
2205            btn.Bind(wx.EVT_BUTTON,onToggle)
2206            btnSizer.Add(btn,0,wx.ALIGN_CENTER)
2207            btnSizer.Add((-1,15))
2208            btnSizer.Add(
2209                wx.StaticText(RBImpPnl,wx.ID_ANY,'Reorient using selected\natoms...'),
2210                0,wx.ALL)
2211            btnSizer.Add((-1,5))
2212            btn = wx.Button(RBImpPnl, wx.ID_OK, 'Set origin')
2213            btn.Bind(wx.EVT_BUTTON,onSetOrigin)
2214            btnSizer.Add(btn,0,wx.ALIGN_CENTER)
2215            btn = wx.Button(RBImpPnl, wx.ID_OK, 'Place in xy plane')
2216            btn.Bind(wx.EVT_BUTTON,onSetPlane)
2217            btnSizer.Add(btn,0,wx.ALIGN_CENTER)
2218            btn = wx.Button(RBImpPnl, wx.ID_OK, 'Define selection as X')
2219            btn.Bind(wx.EVT_BUTTON,onSetX)
2220            btnSizer.Add(btn,0,wx.ALIGN_CENTER)
2221            btnSizer.Add((-1,15))
2222            btnSizer.Add(
2223                wx.StaticText(RBImpPnl,wx.ID_ANY,'Use selected atoms to\ncreate...'),
2224                0,wx.ALL)
2225            btnSizer.Add((-1,5))
2226            btn = wx.Button(RBImpPnl, wx.ID_OK, 'a Vector Body')
2227            btn.Bind(wx.EVT_BUTTON,onAddVector)
2228            btnSizer.Add(btn,0,wx.ALIGN_CENTER)
2229            btn = wx.Button(RBImpPnl, wx.ID_OK, 'a Residue Body')
2230            btn.Bind(wx.EVT_BUTTON,onAddResidue)
2231            btnSizer.Add(btn,0,wx.ALIGN_CENTER)
2232            btn = wx.Button(RBImpPnl, wx.ID_CANCEL)
2233            btn.Bind(wx.EVT_BUTTON,onCancel)
2234            btnSizer.Add((-1,10))
2235            btnSizer.Add(btn,0,wx.ALIGN_CENTER)
2236
2237            mainSizer.Add(btnSizer)
2238            RBImpPnl.SetSizer(mainSizer,True)
2239            mainSizer.Layout()   
2240            Size = mainSizer.GetMinSize()
2241            Size[0] += 40
2242            Size[1] = max(Size[1],G2frame.GetSize()[1]-200) + 20
2243            RBImpPnl.SetSize(Size)
2244            RBImp.SetScrollbars(10,10,Size[0]/10-4,Size[1]/10-1)
2245            RBImp.Scroll(0,0)
2246
2247        def GetCoords(atmsel):
2248            '''Create orthogonal coordinates for selected atoms.
2249            Place the origin at the center of the body
2250            '''
2251            atms = rd.Phase['Atoms']
2252            cell = rd.Phase['General']['Cell'][1:7]
2253            Amat,Bmat = G2lat.cell2AB(cell)
2254            rd.Phase['RBcoords'] = np.array([np.inner(Amat,atms[i][3:6]) for i in atmsel])
2255            rd.Phase['RBcoords'] -= rd.Phase['RBcoords'].mean(axis=0)  # origin to middle
2256            rd.Phase['RBtypes'] = [atms[i][1] for i in atmsel]
2257            rd.Phase['RBlbls'] = [atms[i][0] for i in atmsel]
2258
2259        def UpdateVectorBody(rb,useSelection=False):
2260            '''Put the atoms in order to pass for plotting or for storage as
2261            a vector rigid body.
2262
2263            :param dict rb: rigid body contents created in :func:`MakeVectorBody`
2264            :param bool useSelection: True if the rd.Phase['RBselection']
2265              values will be used to select which atoms are included in the
2266              rigid body. If False (default) they are included in rb
2267              and are used for plotting.         
2268            '''
2269            coordlist = []
2270            typeslist = []
2271            sellist = []
2272            for i in rd.Phase['RBindex']:
2273                use = True
2274                if useSelection and not rd.Phase['RBselection'][i]: use = False
2275                if use:
2276                    coordlist.append(rd.Phase['RBcoords'][i])
2277                    typeslist.append(rd.Phase['RBtypes'][i])
2278                    sellist.append(rd.Phase['RBselection'][i])
2279            coordlist = np.array(coordlist)
2280            rb['rbXYZ'] = coordlist
2281            rb['rbVect'] = [coordlist]
2282            rb['rbTypes'] = typeslist
2283            if not useSelection:
2284                rb['Selection'] = sellist
2285            elif 'Selection' in rb:
2286                del rb['Selection']
2287
2288        def MakeVectorBody(name=''):
2289            '''Make the basic vector rigid body dict (w/o coordinates) used for
2290            export and for plotting
2291            '''
2292            nTrans = 1
2293            vecMag = [1.0]
2294            vecRef = [False]
2295            rb = {'RBname':name,'VectMag':vecMag,
2296                    'rbRef':[0,1,2,False],'VectRef':vecRef,
2297                    'useCount':0}
2298            UpdateVectorBody(rb)
2299            return rb
2300
2301        # get importer type and a phase file of that type
2302        G2sc.LoadG2fil()
2303        choices = [rd.formatName for  rd in G2sc.Readers['Phase']] 
2304        dlg = G2G.G2SingleChoiceDialog(G2frame,'Select the format of the file',
2305                                     'select format',choices)
2306        try:
2307            if dlg.ShowModal() == wx.ID_OK:
2308                col = dlg.GetSelection()
2309            else:
2310                col = None
2311        finally:
2312            dlg.Destroy()
2313        reader = G2sc.Readers['Phase'][col]
2314
2315        choices = reader.formatName + " file ("
2316        w = ""
2317        for extn in reader.extensionlist:
2318            if w != "": w += ";"
2319            w += "*" + extn
2320        choices += w + ")|" + w
2321        #choices += "|zip archive (.zip)|*.zip"
2322        if not reader.strictExtension:
2323            choices += "|any file (*.*)|*.*"
2324        typ = '( type '+reader.formatName+')'
2325        filelist = G2G.GetImportFile(G2frame,
2326                        message="Choose phase input file"+typ,
2327                        defaultFile="",wildcard=choices,style=wx.FD_OPEN)
2328        if len(filelist) != 1: return
2329
2330        # read in the phase file
2331        filename = filelist[0]
2332        rd = reader
2333        with open(filename, 'Ur') as fp:
2334            rd.ReInitialize()
2335            rd.errors = ""
2336            if not rd.ContentsValidator(filename):   # Report error
2337                G2fil.G2Print("Warning: File {} has a validation error".format(filename))
2338                return
2339            if len(rd.selections) > 1:
2340                print("File {} has {} phases. This is unexpected."
2341                                    .format(filename,len(rd.selections)))
2342                return
2343
2344            rd.objname = os.path.basename(filename)
2345            try:
2346                flag = rd.Reader(filename)
2347            except:
2348                G2fil.G2Print("Warning: read of file {} failed".format(filename))
2349                return
2350
2351        pagename = 'Rigid body importer'
2352        Page1()
2353        return
2354
2355    def AddVectTrans(event):
2356        'Add a translation to an existing vector rigid body'
2357        choices = []
2358        rbIdlist = []
2359        for rbid in data['RBIds']['Vector']:
2360            if rbid != 'AtInfo':
2361                rbIdlist.append(rbid)
2362                choices.append(data['Vector'][rbid]['RBname'])
2363        if len(choices) == 0:
2364            G2G.G2MessageBox(G2frame,'No Vector Rigid Bodies found',
2365                                 'No VR Bodies')
2366            return
2367        elif len(choices) == 1:
2368            rbid = rbIdlist[0]
2369        else:
2370            dlg = G2G.G2SingleChoiceDialog(G2frame,'Select the rigid body to save',
2371                                  'select format',choices)
2372            try:
2373                if dlg.ShowModal() == wx.ID_OK:
2374                    rbid = rbIdlist[dlg.GetSelection()]
2375                else:
2376                    return
2377            finally:
2378                dlg.Destroy()
2379        data['Vector'][rbid]['VectMag'] += [1.0]
2380        data['Vector'][rbid]['VectRef'] += [False]
2381        nAtoms = len(data['Vector'][rbid]['rbXYZ'])
2382        data['Vector'][rbid]['rbVect'] += [np.zeros((nAtoms,3))]
2383        UpdateVectorRB()
2384       
2385    def SaveVectorRB(event):
2386        choices = []
2387        rbIdlist = []
2388        for rbid in data['RBIds']['Vector']:
2389            if rbid != 'AtInfo':
2390                rbIdlist.append(rbid)
2391                choices.append(data['Vector'][rbid]['RBname'])
2392        if len(choices) == 0:
2393            G2G.G2MessageBox(G2frame,'No Vector Rigid Bodies found',
2394                                 'No VR Bodies')
2395            return
2396        elif len(choices) == 1:
2397            rbid = rbIdlist[0]
2398        else:
2399            dlg = G2G.G2SingleChoiceDialog(G2frame,'Select the rigid body to save',
2400                                  'select format',choices)
2401            try:
2402                if dlg.ShowModal() == wx.ID_OK:
2403                    rbid = rbIdlist[dlg.GetSelection()]
2404                else:
2405                    return
2406            finally:
2407                dlg.Destroy()
2408             
2409        pth = G2G.GetExportPath(G2frame)
2410        dlg = wx.FileDialog(G2frame, 'Choose file to save vector rigid body',
2411            pth, '', 'VRB files (*.vecbody)|*.vecbody',
2412            wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)
2413        try:
2414            if dlg.ShowModal() == wx.ID_OK:
2415                filename = dlg.GetPath()
2416                filename = os.path.splitext(filename)[0]+'.vecbody'  # set extension
2417                fp = open(filename,'w')
2418                fp.write('Name: '+data['Vector'][rbid]['RBname']+'\n')
2419                fp.write('Trans: ')
2420                for i in data['Vector'][rbid]['VectMag']:
2421                    fp.write(str(i)+" ") 
2422                fp.write('\n')
2423                ntrans = len(data['Vector'][rbid]['VectMag'])
2424                for i,sym in enumerate(data['Vector'][rbid]['rbTypes']):
2425                    fp.write("{:3s}".format(sym))
2426                    for j in range(ntrans):
2427                        fp.write('{:8.5f}{:9.5f}{:9.5f}   '
2428                            .format(*data['Vector'][rbid]['rbVect'][j][i]))
2429                    fp.write('\n')
2430                fp.close()
2431                print ('Vector rigid body saved to: '+filename)
2432        finally:
2433            dlg.Destroy()
2434           
2435    def ReadVectorRB(event):
2436        AtInfo = data['Vector']['AtInfo']
2437        pth = G2G.GetExportPath(G2frame)
2438        dlg = wx.FileDialog(G2frame, 'Choose file to read vector rigid body',
2439            pth, '', 'VRB files (*.vecbody)|*.vecbody',
2440            wx.FD_OPEN)
2441        try:
2442            if dlg.ShowModal() == wx.ID_OK:
2443                filename = dlg.GetPath()
2444                filename = os.path.splitext(filename)[0]+'.vecbody'  # set extension
2445                fp = open(filename,'r')
2446                l = fp.readline().strip()
2447                if 'Name' not in l:
2448                    fp.close()
2449                    G2frame.ErrorDialog('Read Error',
2450                        'File '+filename+' does not start with Name\nFirst line ='
2451                        +l+'\ninvalid file',parent=G2frame)
2452                    return
2453                name = l.split(':')[1].strip()
2454                trans = fp.readline().strip().split(':')[1].split()
2455                vecMag = [float(i) for i in trans]
2456                ntrans = len(trans)
2457                vecs = [[] for i in range(ntrans)]
2458                types = []
2459                l = fp.readline().strip()
2460                while l:
2461                    nums = l.strip().split()
2462                    types.append(nums.pop(0))
2463                    t = types[-1]
2464                    if t not in AtInfo:
2465                        Info = G2elem.GetAtomInfo(t)
2466                        AtInfo[t] = [Info['Drad'],Info['Color']]
2467                    for i in range(ntrans):
2468                        vecs[i].append([float(nums.pop(0)) for j in range(3)])
2469                    l = fp.readline().strip()
2470                fp.close()
2471            else:
2472                return       
2473        finally:
2474            dlg.Destroy()
2475        natoms = len(types)
2476        vecs = [np.array(vecs[i]) for i in range(ntrans)]
2477        rbid = ran.randint(0,sys.maxsize)
2478        data['Vector'][rbid] = {'RBname':name,'VectMag':vecMag,
2479                'rbXYZ':np.zeros((natoms,3)),
2480                'rbRef':[0,1,2,False],'VectRef':ntrans*[False],
2481                'rbTypes':types,
2482                'rbVect':vecs,'useCount':0}
2483        data['RBIds']['Vector'].append(rbid)
2484        UpdateVectorRB()
2485       
2486    def AddResidueRB(event):
2487        global resRBsel
2488        AtInfo = data['Residue']['AtInfo']
2489        macro = getMacroFile('rigid body')
2490        if not macro:
2491            return
2492        macStr = macro.readline()
2493        while macStr:
2494            items = macStr.split()
2495            if 'I' == items[0]:
2496                resRBsel = ran.randint(0,sys.maxsize)
2497                rbName = items[1]
2498                rbTypes = []
2499                rbXYZ = []
2500                rbSeq = []
2501                atNames = []
2502                nAtms,nSeq,nOrig,mRef,nRef = [int(items[i]) for i in [2,3,4,5,6]]
2503                for iAtm in range(nAtms):
2504                    macStr = macro.readline().split()
2505                    atName = macStr[0]
2506                    atType = macStr[1]
2507                    atNames.append(atName)
2508                    rbXYZ.append([float(macStr[i]) for i in [2,3,4]])
2509                    rbTypes.append(atType)
2510                    if atType not in AtInfo:
2511                        Info = G2elem.GetAtomInfo(atType)
2512                        AtInfo[atType] = [Info['Drad'],Info['Color']]
2513                rbXYZ = np.array(rbXYZ)-np.array(rbXYZ[nOrig-1])
2514                for iSeq in range(nSeq):
2515                    macStr = macro.readline().split()
2516                    mSeq = int(macStr[0])
2517                    for jSeq in range(mSeq):
2518                        macStr = macro.readline().split()
2519                        iBeg = int(macStr[0])-1
2520                        iFin = int(macStr[1])-1
2521                        angle = 0.0
2522                        nMove = int(macStr[2])
2523                        iMove = [int(macStr[i])-1 for i in range(3,nMove+3)]
2524                        rbSeq.append([iBeg,iFin,angle,iMove])
2525                data['Residue'][resRBsel] = {'RBname':rbName,'rbXYZ':rbXYZ,'rbTypes':rbTypes,
2526                    'atNames':atNames,'rbRef':[nOrig-1,mRef-1,nRef-1,True],'rbSeq':rbSeq,
2527                    'SelSeq':[0,0],'useCount':0}
2528                data['RBIds']['Residue'].append(resRBsel)
2529                print ('Rigid body '+rbName+' added')
2530            macStr = macro.readline()
2531        macro.close()
2532        UpdateResidueRB()
2533       
2534    def ImportResidueRB():
2535        global resRBsel
2536        AtInfo = data['Residue']['AtInfo']
2537        text,ext = getTextFile()
2538        if not text:
2539            return
2540        resRBsel = ran.randint(0,sys.maxsize)
2541        rbTypes = []
2542        rbXYZ = []
2543        atNames = []
2544        txtStr = text.readline()
2545        if 'xyz' in ext:
2546            txtStr = text.readline()
2547            txtStr = text.readline()
2548        elif 'mol2' in ext:
2549            while 'ATOM' not in txtStr:
2550                txtStr = text.readline()
2551            txtStr = text.readline()
2552        elif 'pdb' in ext:
2553            while 'ATOM' not in txtStr[:6] and 'HETATM' not in txtStr[:6]:
2554                txtStr = text.readline()
2555        items = txtStr.split()
2556        nat = 1
2557        while len(items):
2558            if 'txt' in ext:
2559                atName = items[0]
2560                atType = items[1]
2561                rbXYZ.append([float(items[i]) for i in [2,3,4]])
2562            elif 'xyz' in ext:
2563                atType = items[0]
2564                rbXYZ.append([float(items[i]) for i in [1,2,3]])
2565                atName = '%s%d'%(atType,nat)
2566            elif 'mol2' in ext:
2567                atType = items[1]
2568                atName = items[1]+items[0]
2569                rbXYZ.append([float(items[i]) for i in [2,3,4]])
2570            elif 'pdb' in ext:
2571                atType = items[-1]
2572                if not items[2][-1].isnumeric():
2573                    atName = '%s%d'%(items[2],nat)
2574                else:
2575                    atName = '5s'%items[2]
2576                xyz = txtStr[30:55].split()                   
2577                rbXYZ.append([float(x) for x in xyz])
2578            atNames.append(atName)
2579            rbTypes.append(atType)
2580            if atType not in AtInfo:
2581                Info = G2elem.GetAtomInfo(atType)
2582                AtInfo[atType] = [Info['Drad'],Info['Color']]
2583            txtStr = text.readline()
2584            if 'mol2' in ext and 'BOND' in txtStr:
2585                break
2586            if 'pdb' in ext and ('ATOM' not in txtStr[:6] and 'HETATM' not in txtStr[:6]):
2587                break
2588            items = txtStr.split()
2589            nat += 1
2590        if len(atNames) < 3:
2591            G2G.G2MessageBox(G2frame,'Not enough atoms in rigid body; must be 3 or more')
2592        else:
2593            rbXYZ = np.array(rbXYZ)-np.array(rbXYZ[0])
2594            Xxyz = rbXYZ[1]
2595            X = Xxyz/np.sqrt(np.sum(Xxyz**2))
2596            Yxyz = rbXYZ[2]
2597            Y = Yxyz/np.sqrt(np.sum(Yxyz**2))
2598            Mat = G2mth.getRBTransMat(X,Y)
2599            rbXYZ = np.inner(Mat,rbXYZ).T
2600            data['Residue'][resRBsel] = {'RBname':'UNKRB','rbXYZ':rbXYZ,'rbTypes':rbTypes,
2601                'atNames':atNames,'rbRef':[0,1,2,False],'rbSeq':[],'SelSeq':[0,0],'useCount':0}
2602            data['RBIds']['Residue'].append(resRBsel)
2603            print ('Rigid body UNKRB added')
2604        text.close()
2605        UpdateResidueRB()
2606       
2607    def SaveResidueRB():
2608        global resRBsel
2609        pth = G2G.GetExportPath(G2frame)
2610        dlg = wx.FileDialog(G2frame, 'Choose PDB file for Atom XYZ', pth, '', 
2611            'PDB files (*.pdb)|*.pdb',wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)
2612        try:
2613            if dlg.ShowModal() == wx.ID_OK:
2614                filename = dlg.GetPath()
2615                filename = os.path.splitext(filename)[0]+'.pdb'  # make extension .pdb
2616                File = open(filename,'w')       
2617                rbData =  data['Residue'][resRBsel]
2618                for iat,xyz in enumerate(rbData['rbXYZ']):
2619                    File.write('ATOM %6d  %-4s%3s     1    %8.3f%8.3f%8.3f  1.00  0.00          %2s\n'%(
2620                        iat,rbData['atNames'][iat],rbData['RBname'][:3],xyz[0],xyz[1],xyz[2],rbData['rbTypes'][iat]))
2621                File.close()
2622                print ('Atom XYZ saved to: '+filename)
2623        finally:
2624            dlg.Destroy()
2625       
2626       
2627    def FindNeighbors(Orig,XYZ,atTypes,atNames,AtInfo):
2628        Radii = []
2629        for Atype in atTypes:
2630            Radii.append(AtInfo[Atype][0])
2631        Radii = np.array(Radii)
2632        Neigh = []
2633        Dx = XYZ-XYZ[Orig]
2634        dist = np.sqrt(np.sum(Dx**2,axis=1))
2635        sumR = Radii[Orig]+Radii
2636        IndB = ma.nonzero(ma.masked_greater(dist-0.85*sumR,0.))
2637        for j in IndB[0]:
2638            if j != Orig and atTypes[j] != 'H':
2639                Neigh.append(atNames[j])
2640        return Neigh
2641       
2642    def FindAllNeighbors(XYZ,atTypes,atNames,AtInfo):
2643        NeighDict = {}
2644        for iat,xyz in enumerate(atNames):
2645            NeighDict[atNames[iat]] = FindNeighbors(iat,XYZ,atTypes,atNames,AtInfo)
2646        return NeighDict
2647       
2648    def FindRiding(Orig,Pivot,NeighDict):
2649        riding = [Orig,Pivot]
2650        iAdd = 1
2651        new = True
2652        while new:
2653            newAtms = NeighDict[riding[iAdd]]
2654            for At in newAtms:
2655                new = False
2656                if At not in riding:
2657                    riding.append(At)
2658                    new = True
2659            iAdd += 1
2660            if iAdd < len(riding):
2661                new = True
2662        return riding[2:]
2663                       
2664    def OnDefineTorsSeq(event):
2665        global resRBsel
2666        rbData = data['Residue'][resRBsel]
2667        if not len(rbData):
2668            return
2669        atNames = rbData['atNames']
2670        AtInfo = data['Residue']['AtInfo']
2671        atTypes = rbData['rbTypes']
2672        XYZ = rbData['rbXYZ']
2673        neighDict = FindAllNeighbors(XYZ,atTypes,atNames,AtInfo)
2674        TargList = []           
2675        dlg = wx.SingleChoiceDialog(G2frame,'Select origin atom for torsion sequence','Origin atom',rbData['atNames'])
2676        if dlg.ShowModal() == wx.ID_OK:
2677            Orig = dlg.GetSelection()
2678            TargList = neighDict[atNames[Orig]]
2679        dlg.Destroy()
2680        if not len(TargList):
2681            return
2682        dlg = wx.SingleChoiceDialog(G2frame,'Select pivot atom for torsion sequence','Pivot atom',TargList)
2683        if dlg.ShowModal() == wx.ID_OK:
2684            Piv = atNames.index(TargList[dlg.GetSelection()])
2685            riding = FindRiding(atNames[Orig],atNames[Piv],neighDict)
2686            Riding = []
2687            for atm in riding:
2688                Riding.append(atNames.index(atm))
2689            rbData['rbSeq'].append([Orig,Piv,0.0,Riding])           
2690        dlg.Destroy()
2691        UpdateResidueRB()
2692
2693    def UpdateVectorRB(Scroll=0):
2694        AtInfo = data['Vector']['AtInfo']
2695        refChoice = {}
2696        if 'DELETED' in str(G2frame.GetStatusBar()):   #seems to be no other way to do this (wx bug)
2697            if GSASIIpath.GetConfigValue('debug'):
2698                print ('DBG_wx error: Rigid Body/Status not cleanly deleted after Refine')
2699            return
2700        SetStatusLine(' You may use e.g. "c60" or "s60" for a vector entry')
2701        def rbNameSizer(rbid,rbData):
2702
2703            def OnRBName(event):
2704                event.Skip()
2705                Obj = event.GetEventObject()
2706                rbData['RBname'] = Obj.GetValue()
2707               
2708            def OnDelRB(event):
2709                Obj = event.GetEventObject()
2710                rbid = Indx[Obj.GetId()]
2711                if rbid in data['Vector']:
2712                    del data['Vector'][rbid]
2713                    data['RBIds']['Vector'].remove(rbid)
2714                    rbData['useCount'] -= 1
2715                wx.CallAfter(UpdateVectorRB)
2716            def OnPlotRB(event):
2717                Obj = event.GetEventObject()
2718                G2plt.PlotRigidBody(G2frame,'Vector',AtInfo,rbData,plotDefaults)
2719           
2720            nameSizer = wx.BoxSizer(wx.HORIZONTAL)
2721            nameSizer.Add(wx.StaticText(VectorRBDisplay,-1,'Rigid body name: '),
2722                0,wx.ALIGN_CENTER_VERTICAL)
2723            RBname = wx.TextCtrl(VectorRBDisplay,-1,rbData['RBname'])
2724            Indx[RBname.GetId()] = rbid
2725            RBname.Bind(wx.EVT_TEXT_ENTER,OnRBName)
2726            RBname.Bind(wx.EVT_KILL_FOCUS,OnRBName)
2727            nameSizer.Add(RBname,0,wx.ALIGN_CENTER_VERTICAL)
2728            nameSizer.Add((5,0),)
2729            plotRB =  wx.Button(VectorRBDisplay,wx.ID_ANY,'Plot',
2730                                style=wx.BU_EXACTFIT)
2731            plotRB.Bind(wx.EVT_BUTTON, OnPlotRB)
2732            Indx[plotRB.GetId()] = rbid
2733            nameSizer.Add(plotRB,0,wx.ALIGN_CENTER_VERTICAL)
2734            nameSizer.Add((5,0),)
2735            if not rbData['useCount']:
2736                delRB = wx.Button(VectorRBDisplay,wx.ID_ANY,"Delete",
2737                                style=wx.BU_EXACTFIT)
2738                delRB.Bind(wx.EVT_BUTTON, OnDelRB)
2739                Indx[delRB.GetId()] = rbid
2740                nameSizer.Add(delRB,0,wx.ALIGN_CENTER_VERTICAL)
2741            return nameSizer
2742           
2743        def rbRefAtmSizer(rbid,rbData):
2744           
2745            def OnRefSel(event):
2746                Obj = event.GetEventObject()
2747                iref = Indx[Obj.GetId()]
2748                sel = Obj.GetValue()
2749                rbData['rbRef'][iref] = atNames.index(sel)
2750                FillRefChoice(rbid,rbData)
2751           
2752            refAtmSizer = wx.BoxSizer(wx.HORIZONTAL)
2753            atNames = [name+str(i) for i,name in enumerate(rbData['rbTypes'])]
2754            rbRef = rbData.get('rbRef',[0,1,2,False])
2755            rbData['rbRef'] = rbRef
2756            if rbData['useCount']:
2757                refAtmSizer.Add(wx.StaticText(VectorRBDisplay,-1,
2758                    'Orientation reference atoms A-B-C: %s, %s, %s'%(atNames[rbRef[0]], \
2759                     atNames[rbRef[1]],atNames[rbRef[2]])),0)
2760            else:
2761                refAtmSizer.Add(wx.StaticText(VectorRBDisplay,-1,
2762                    'Orientation reference atoms A-B-C: '),0,wx.ALIGN_CENTER_VERTICAL)
2763                for i in range(3):
2764                    choices = [atNames[j] for j in refChoice[rbid][i]]
2765                    refSel = wx.ComboBox(VectorRBDisplay,-1,value='',
2766                        choices=choices,style=wx.CB_READONLY|wx.CB_DROPDOWN)
2767                    refSel.SetValue(atNames[rbRef[i]])
2768                    refSel.Bind(wx.EVT_COMBOBOX, OnRefSel)
2769                    Indx[refSel.GetId()] = i
2770                    refAtmSizer.Add(refSel,0,wx.ALIGN_CENTER_VERTICAL)
2771            return refAtmSizer
2772                       
2773        def rbVectMag(rbid,imag,rbData):
2774           
2775            def OnRBVectorMag(event):
2776                event.Skip()
2777                Obj = event.GetEventObject()
2778                rbid,imag = Indx[Obj.GetId()]
2779                try:
2780                    val = float(Obj.GetValue())
2781                    if val <= 0.:
2782                        raise ValueError
2783                    rbData['VectMag'][imag] = val
2784                except ValueError:
2785                    pass
2786                Obj.SetValue('%8.4f'%(val))
2787                wx.CallAfter(UpdateVectorRB,VectorRB.GetScrollPos(wx.VERTICAL))
2788                G2plt.PlotRigidBody(G2frame,'Vector',AtInfo,data['Vector'][rbid],plotDefaults)
2789               
2790            def OnRBVectorRef(event):
2791                Obj = event.GetEventObject()
2792                rbid,imag = Indx[Obj.GetId()]
2793                rbData['VectRef'][imag] = Obj.GetValue()
2794                       
2795            magSizer = wx.BoxSizer(wx.HORIZONTAL)
2796            magSizer.Add(wx.StaticText(VectorRBDisplay,-1,'Translation magnitude: '),
2797                0,wx.ALIGN_CENTER_VERTICAL)
2798            magValue = wx.TextCtrl(VectorRBDisplay,-1,'%8.4f'%(rbData['VectMag'][imag]))
2799            Indx[magValue.GetId()] = [rbid,imag]
2800            magValue.Bind(wx.EVT_TEXT_ENTER,OnRBVectorMag)
2801            magValue.Bind(wx.EVT_KILL_FOCUS,OnRBVectorMag)
2802            magSizer.Add(magValue,0,wx.ALIGN_CENTER_VERTICAL)
2803            magSizer.Add((5,0),)
2804            magref = wx.CheckBox(VectorRBDisplay,-1,label=' Refine?') 
2805            magref.SetValue(rbData['VectRef'][imag])
2806            magref.Bind(wx.EVT_CHECKBOX,OnRBVectorRef)
2807            Indx[magref.GetId()] = [rbid,imag]
2808            magSizer.Add(magref,0,wx.ALIGN_CENTER_VERTICAL)
2809            return magSizer
2810           
2811        def rbVectors(rbid,imag,mag,XYZ,rbData):
2812
2813            def TypeSelect(event):
2814                AtInfo = data['Vector']['AtInfo']
2815                r,c = event.GetRow(),event.GetCol()
2816                if vecGrid.GetColLabelValue(c) == 'Type':
2817                    PE = G2elemGUI.PickElement(G2frame,oneOnly=True)
2818                    if PE.ShowModal() == wx.ID_OK:
2819                        if PE.Elem != 'None':
2820                            El = PE.Elem.strip().lower().capitalize()
2821                            if El not in AtInfo:
2822                                Info = G2elem.GetAtomInfo(El)
2823                                AtInfo[El] = [Info['Drad'],Info['Color']]
2824                            rbData['rbTypes'][r] = El
2825                            vecGrid.SetCellValue(r,c,El)
2826                    PE.Destroy()
2827                wx.CallAfter(UpdateVectorRB,VectorRB.GetScrollPos(wx.VERTICAL))
2828
2829            def ChangeCell(event):
2830                r,c =  event.GetRow(),event.GetCol()
2831                if r >= 0 and (0 <= c < 3):
2832                    try:
2833                        val = float(vecGrid.GetCellValue(r,c))
2834                        rbData['rbVect'][imag][r][c] = val
2835                    except ValueError:
2836                        pass
2837                G2plt.PlotRigidBody(G2frame,'Vector',AtInfo,data['Vector'][rbid],plotDefaults)
2838                wx.CallAfter(UpdateVectorRB,VectorRB.GetScrollPos(wx.VERTICAL))
2839
2840            vecSizer = wx.BoxSizer()
2841            Types = 3*[wg.GRID_VALUE_FLOAT+':10,5',]+[wg.GRID_VALUE_STRING,]+3*[wg.GRID_VALUE_FLOAT+':10,5',]
2842            colLabels = ['Vector x','Vector y','Vector z','Type','Cart x','Cart y','Cart z']
2843            table = []
2844            rowLabels = []
2845            for ivec,xyz in enumerate(rbData['rbVect'][imag]):
2846                table.append(list(xyz)+[rbData['rbTypes'][ivec],]+list(XYZ[ivec]))
2847                rowLabels.append(str(ivec))
2848            vecTable = G2G.Table(table,rowLabels=rowLabels,colLabels=colLabels,types=Types)
2849            vecGrid = G2G.GSGrid(VectorRBDisplay)
2850            vecGrid.SetTable(vecTable, True)
2851            if 'phoenix' in wx.version():
2852                vecGrid.Bind(wg.EVT_GRID_CELL_CHANGED, ChangeCell)
2853            else:
2854                vecGrid.Bind(wg.EVT_GRID_CELL_CHANGE, ChangeCell)
2855            if not imag:
2856                vecGrid.Bind(wg.EVT_GRID_CELL_LEFT_DCLICK, TypeSelect)
2857            attr = wx.grid.GridCellAttr()
2858            attr.IncRef()
2859            attr.SetEditor(G2G.GridFractionEditor(vecGrid))
2860            for c in range(3):
2861                attr.IncRef()
2862                vecGrid.SetColAttr(c, attr)
2863            for row in range(vecTable.GetNumberRows()):
2864                if imag:
2865                    vecGrid.SetCellStyle(row,3,VERY_LIGHT_GREY,True)                   
2866                for col in [4,5,6]:
2867                    vecGrid.SetCellStyle(row,col,VERY_LIGHT_GREY,True)
2868#            vecGrid.SetScrollRate(0,0)
2869            vecGrid.AutoSizeColumns(False)
2870            vecSizer.Add(vecGrid)
2871            return vecSizer
2872       
2873        def FillRefChoice(rbid,rbData):
2874            choiceIds = [i for i in range(len(rbData['rbTypes']))]
2875           
2876            rbRef = rbData.get('rbRef',[-1,-1,-1,False])
2877            for i in range(3):
2878                choiceIds.remove(rbRef[i])
2879            refChoice[rbid] = [choiceIds[:],choiceIds[:],choiceIds[:]]
2880            for i in range(3):
2881                refChoice[rbid][i].append(rbRef[i])
2882                refChoice[rbid][i].sort()     
2883           
2884        if VectorRB.GetSizer(): VectorRB.GetSizer().Clear(True)
2885        VectorRBSizer = wx.BoxSizer(wx.VERTICAL)
2886        first = True
2887        for rbid in data['RBIds']['Vector']:
2888            if rbid != 'AtInfo':
2889                rbData = data['Vector'][rbid]
2890                FillRefChoice(rbid,rbData)
2891                if not first:
2892                    G2G.HorizontalLine(VectorRBSizer,VectorRBDisplay)
2893                VectorRBSizer.Add(rbNameSizer(rbid,rbData),0)
2894                VectorRBSizer.Add(rbRefAtmSizer(rbid,rbData),0)
2895                XYZ = np.array([[0.,0.,0.] for Ty in rbData['rbTypes']])
2896                for imag,mag in enumerate(rbData['VectMag']):
2897                    XYZ += mag*rbData['rbVect'][imag]
2898                    VectorRBSizer.Add(rbVectMag(rbid,imag,rbData),0)
2899                    VectorRBSizer.Add(rbVectors(rbid,imag,mag,XYZ,rbData),0)
2900                VectorRBSizer.Add((5,5),0)
2901                data['Vector'][rbid]['rbXYZ'] = XYZ       
2902                first = False
2903        VectorRBSizer.Layout()   
2904        VectorRBDisplay.SetSizer(VectorRBSizer,True)
2905        Size = VectorRBSizer.GetMinSize()
2906        Size[0] += 40
2907        Size[1] = max(Size[1],450) + 20
2908        VectorRBDisplay.SetSize(Size)
2909        VectorRB.SetScrollbars(10,10,Size[0]/10-4,Size[1]/10-1)
2910        VectorRB.Scroll(0,Scroll)
2911       
2912    def UpdateResidueRB():
2913        '''Draw the contents of the Residue Rigid Body tab for Rigid Bodies tree entry
2914        '''
2915        global resRBsel
2916        def rbNameSizer(rbid,rbData):
2917
2918            def OnDelRB(event):
2919                Obj = event.GetEventObject()
2920                rbid = Indx[Obj.GetId()]
2921                if rbid in data['Residue']: 
2922                    del data['Residue'][rbid]
2923                    data['RBIds']['Residue'].remove(rbid)
2924                wx.CallAfter(UpdateResidueRB)
2925               
2926            def OnStripH(event):
2927                Obj = event.GetEventObject()
2928                rbid = Indx[Obj.GetId()]
2929                if rbid in data['Residue']:
2930                    newNames = []
2931                    newTypes = []
2932                    newXYZ = []
2933                    for i,atype in enumerate(rbData['rbTypes']):
2934                        if atype != 'H':
2935                            newNames.append(rbData['atNames'][i])
2936                            newTypes.append(rbData['rbTypes'][i])
2937                            newXYZ.append(rbData['rbXYZ'][i])
2938                    rbData['atNames'] = newNames
2939                    rbData['rbTypes'] = newTypes
2940                    rbData['rbXYZ'] = newXYZ
2941                G2plt.PlotRigidBody(G2frame,'Residue',AtInfo,rbData,plotDefaults)
2942                wx.CallAfter(UpdateResidueRB)
2943                   
2944            def OnPlotRB(event):
2945                Obj = event.GetEventObject()
2946                G2plt.PlotRigidBody(G2frame,'Residue',AtInfo,rbData,plotDefaults)
2947               
2948            # start of rbNameSizer
2949            nameSizer = wx.BoxSizer(wx.HORIZONTAL)
2950            nameSizer.Add(wx.StaticText(ResidueRBDisplay,-1,'Residue name: '),
2951                0,wx.ALIGN_CENTER_VERTICAL)
2952            nameSizer.Add(G2G.ValidatedTxtCtrl(ResidueRBDisplay,rbData,'RBname'),0,WACV)
2953            nameSizer.Add((5,0),)
2954            plotRB =  wx.Button(ResidueRBDisplay,wx.ID_ANY,'Plot',
2955                                style=wx.BU_EXACTFIT)
2956            plotRB.Bind(wx.EVT_BUTTON, OnPlotRB)
2957            Indx[plotRB.GetId()] = rbid
2958            nameSizer.Add(plotRB,0,wx.ALIGN_CENTER_VERTICAL)
2959            nameSizer.Add((5,0),)
2960            if not rbData['useCount']:
2961                delRB = wx.Button(ResidueRBDisplay,wx.ID_ANY,"Delete",
2962                                style=wx.BU_EXACTFIT)
2963                delRB.Bind(wx.EVT_BUTTON, OnDelRB)
2964                Indx[delRB.GetId()] = rbid
2965                nameSizer.Add(delRB,0,wx.ALIGN_CENTER_VERTICAL)
2966                if 'H'  in rbData['rbTypes']:
2967                    stripH = wx.Button(ResidueRBDisplay,wx.ID_ANY,
2968                                "Strip H-atoms",
2969                                style=wx.BU_EXACTFIT)
2970                    stripH.Bind(wx.EVT_BUTTON, OnStripH)
2971                    Indx[stripH.GetId()] = rbid
2972                    nameSizer.Add(stripH,0,wx.ALIGN_CENTER_VERTICAL)
2973            return nameSizer
2974           
2975        def rbResidues(rbid,rbData):
2976           
2977            def TypeSelect(event):
2978                AtInfo = data['Residue']['AtInfo']
2979                r,c = event.GetRow(),event.GetCol()
2980                if resGrid.GetColLabelValue(c) == 'Type':
2981                    PE = G2elemGUI.PickElement(G2frame,oneOnly=True)
2982                    if PE.ShowModal() == wx.ID_OK:
2983                        if PE.Elem != 'None':
2984                            El = PE.Elem.strip().lower().capitalize()
2985                            if El not in AtInfo:
2986                                Info = G2elem.GetAtomInfo(El)
2987                                AtInfo[El] = [Info['Drad']['Color']]
2988                            rbData['rbTypes'][r] = El
2989                            resGrid.SetCellValue(r,c,El)
2990                    PE.Destroy()
2991
2992            def ChangeCell(event):
2993                r,c =  event.GetRow(),event.GetCol()
2994                if r >= 0 and (0 <= c < 3):
2995                    try:
2996                        val = float(resGrid.GetCellValue(r,c))
2997                        rbData['rbXYZ'][r][c] = val
2998                    except ValueError:
2999                        pass
3000                       
3001            def RowSelect(event):
3002                r,c =  event.GetRow(),event.GetCol()
3003                if c < 0:                   #only row clicks
3004                    for iGrid in resList:
3005                        iGrid.ClearSelection()
3006                    resGrid.SelectRow(r,True)
3007
3008            def OnRefSel(event):
3009                Obj = event.GetEventObject()
3010                iref,res,jref = Indx[Obj.GetId()]
3011                sel = Obj.GetValue()
3012                ind = atNames.index(sel)
3013                if rbData['rbTypes'][ind] == 'H':
3014                    G2G.G2MessageBox(G2frame,'You should not select an H-atom for rigid body orientation')
3015                rbData['rbRef'][iref] = ind
3016                FillRefChoice(rbid,rbData)
3017                for i,ref in enumerate(RefObjs[jref]):
3018                    ref.SetItems([atNames[j] for j in refChoice[rbid][i]])
3019                    ref.SetValue(atNames[rbData['rbRef'][i]])                   
3020                rbXYZ = rbData['rbXYZ']
3021                if not iref:     #origin change
3022                    rbXYZ -= rbXYZ[ind]
3023                #TODO - transform all atom XYZ by axis choices
3024                Xxyz = rbXYZ[rbData['rbRef'][1]]
3025                X = Xxyz/np.sqrt(np.sum(Xxyz**2))
3026                Yxyz = rbXYZ[rbData['rbRef'][2]]
3027                Y = Yxyz/np.sqrt(np.sum(Yxyz**2))
3028                Mat = G2mth.getRBTransMat(X,Y)
3029                rbXYZ = np.inner(Mat,rbXYZ).T
3030                rbData['rbXYZ'] = rbXYZ
3031                res.ClearSelection()
3032                resTable = res.GetTable()
3033                for r in range(res.GetNumberRows()):
3034                    row = resTable.GetRowValues(r)
3035                    row[2:4] = rbXYZ[r]
3036                    resTable.SetRowValues(r,row)
3037                res.ForceRefresh()
3038                G2plt.PlotRigidBody(G2frame,'Residue',AtInfo,rbData,plotDefaults)
3039               
3040            Types = 2*[wg.GRID_VALUE_STRING,]+3*[wg.GRID_VALUE_FLOAT+':10,5',]
3041            colLabels = ['Name','Type','Cart x','Cart y','Cart z']
3042            table = []
3043            rowLabels = []
3044            for ivec,xyz in enumerate(rbData['rbXYZ']):
3045                table.append([rbData['atNames'][ivec],]+[rbData['rbTypes'][ivec],]+list(xyz))
3046                rowLabels.append(str(ivec))
3047            vecTable = G2G.Table(table,rowLabels=rowLabels,colLabels=colLabels,types=Types)
3048            resGrid = G2G.GSGrid(ResidueRBDisplay)
3049            Indx[resGrid.GetId()] = rbid
3050            resList.append(resGrid)
3051            resGrid.SetTable(vecTable, True)
3052            if 'phoenix' in wx.version():
3053                resGrid.Bind(wg.EVT_GRID_CELL_CHANGED, ChangeCell)
3054            else:
3055                resGrid.Bind(wg.EVT_GRID_CELL_CHANGE, ChangeCell)
3056            resGrid.Bind(wg.EVT_GRID_CELL_LEFT_DCLICK, TypeSelect)
3057            resGrid.Bind(wg.EVT_GRID_LABEL_LEFT_CLICK, RowSelect)
3058            attr = wx.grid.GridCellAttr()
3059            attr.IncRef()
3060            attr.SetEditor(G2G.GridFractionEditor(resGrid))
3061            for c in range(3):
3062                attr.IncRef()
3063                resGrid.SetColAttr(c, attr)
3064            for row in range(vecTable.GetNumberRows()):
3065                for col in range(5):
3066                    resGrid.SetCellStyle(row,col,VERY_LIGHT_GREY,True)
3067            resGrid.AutoSizeColumns(False)
3068            vecSizer = wx.BoxSizer()
3069            vecSizer.Add(resGrid)
3070           
3071            refAtmSizer = wx.BoxSizer(wx.HORIZONTAL)
3072            atNames = rbData['atNames']
3073            rbRef = rbData['rbRef']
3074            if rbData['rbRef'][3] or rbData['useCount']:
3075                refAtmSizer.Add(wx.StaticText(ResidueRBDisplay,-1,
3076                    'Orientation reference non-H atoms A-B-C: %s, %s, %s'%(atNames[rbRef[0]], \
3077                     atNames[rbRef[1]],atNames[rbRef[2]])),0)
3078            else:
3079                refAtmSizer.Add(wx.StaticText(ResidueRBDisplay,-1,
3080                    'Orientation reference non-H atoms A-B-C: '),0,wx.ALIGN_CENTER_VERTICAL)
3081                refObj = [0,0,0]
3082                for i in range(3):
3083                    choices = [atNames[j] for j in refChoice[rbid][i]]
3084                    refSel = wx.ComboBox(ResidueRBDisplay,-1,value='',
3085                        choices=choices,style=wx.CB_READONLY|wx.CB_DROPDOWN)
3086                    refSel.SetValue(atNames[rbRef[i]])
3087                    refSel.Bind(wx.EVT_COMBOBOX, OnRefSel)
3088                    Indx[refSel.GetId()] = [i,resGrid,len(RefObjs)]
3089                    refObj[i] = refSel
3090                    refAtmSizer.Add(refSel,0,wx.ALIGN_CENTER_VERTICAL)
3091                RefObjs.append(refObj)
3092           
3093            mainSizer = wx.BoxSizer(wx.VERTICAL)
3094            mainSizer.Add(refAtmSizer)
3095            mainSizer.Add(vecSizer)
3096            return mainSizer
3097           
3098        def Add2SeqSizer(seqSizer,angSlide,rbid,iSeq,Seq,atNames):
3099           
3100            def ChangeAngle(event):
3101                event.Skip()
3102                Obj = event.GetEventObject()
3103                rbid,Seq = Indx[Obj.GetId()][:2]
3104                val = Seq[2]
3105                try:
3106                    val = float(Obj.GetValue())
3107                    Seq[2] = val
3108                except ValueError:
3109                    pass
3110                Obj.SetValue('%8.2f'%(val))
3111                G2plt.PlotRigidBody(G2frame,'Residue',AtInfo,data['Residue'][rbid],plotDefaults)
3112               
3113            def OnRadBtn(event):
3114                Obj = event.GetEventObject()
3115                Seq,iSeq,angId = Indx[Obj.GetId()]
3116                data['Residue'][rbid]['SelSeq'] = [iSeq,angId]
3117                angSlide.SetValue(int(100*Seq[2]))
3118               
3119            def OnDelBtn(event):
3120                Obj = event.GetEventObject()
3121                rbid,Seq = Indx[Obj.GetId()]
3122                data['Residue'][rbid]['rbSeq'].remove(Seq)       
3123                wx.CallAfter(UpdateResidueRB)
3124           
3125            iBeg,iFin,angle,iMove = Seq
3126            ang = wx.TextCtrl(ResidueRBDisplay,wx.ID_ANY,
3127                    '%8.2f'%(angle),size=(70,-1),style=wx.TE_PROCESS_ENTER)
3128            if not iSeq:
3129                radBt = wx.RadioButton(ResidueRBDisplay,wx.ID_ANY,
3130                                           '',style=wx.RB_GROUP)
3131                data['Residue'][rbid]['SelSeq'] = [iSeq,ang.GetId()]
3132                radBt.SetValue(True)
3133            else:
3134                radBt = wx.RadioButton(ResidueRBDisplay,wx.ID_ANY,'')
3135            radBt.Bind(wx.EVT_RADIOBUTTON,OnRadBtn)                   
3136            seqSizer.Add(radBt)
3137            delBt =  wx.Button(ResidueRBDisplay,wx.ID_ANY,'Del',
3138                                style=wx.BU_EXACTFIT)
3139            delBt.Bind(wx.EVT_BUTTON,OnDelBtn)
3140            seqSizer.Add(delBt)
3141            bond = wx.StaticText(ResidueRBDisplay,wx.ID_ANY,
3142                        '%s %s'%(atNames[iBeg],atNames[iFin]),size=(50,20))
3143            seqSizer.Add(bond,0,wx.ALIGN_CENTER_VERTICAL)
3144            Indx[radBt.GetId()] = [Seq,iSeq,ang.GetId()]
3145            Indx[delBt.GetId()] = [rbid,Seq]
3146            Indx[ang.GetId()] = [rbid,Seq,ang]
3147            ang.Bind(wx.EVT_TEXT_ENTER,ChangeAngle)
3148            ang.Bind(wx.EVT_KILL_FOCUS,ChangeAngle)
3149            seqSizer.Add(ang,0,wx.ALIGN_CENTER_VERTICAL)
3150            atms = ''
3151            for i in iMove:   
3152                atms += ' %s,'%(atNames[i])
3153            moves = wx.StaticText(ResidueRBDisplay,wx.ID_ANY,
3154                            atms[:-1],size=(200,20))
3155            seqSizer.Add(moves,1,wx.ALIGN_CENTER_VERTICAL|wx.EXPAND|wx.RIGHT)
3156            return seqSizer
3157           
3158        def SlideSizer():
3159           
3160            def OnSlider(event):
3161                Obj = event.GetEventObject()
3162                rbData = Indx[Obj.GetId()]
3163                iSeq,angId = rbData['SelSeq']
3164                val = float(Obj.GetValue())/100.
3165                rbData['rbSeq'][iSeq][2] = val
3166                Indx[angId][2].SetValue('%8.2f'%(val))
3167                G2plt.PlotRigidBody(G2frame,'Residue',AtInfo,rbData,plotDefaults)
3168           
3169            slideSizer = wx.BoxSizer(wx.HORIZONTAL)
3170            slideSizer.Add(wx.StaticText(ResidueRBDisplay,-1,'Selected torsion angle:'),0)
3171            iSeq,angId = rbData['SelSeq']
3172            angSlide = wx.Slider(ResidueRBDisplay,-1,
3173                int(100*rbData['rbSeq'][iSeq][2]),0,36000,size=(200,20),
3174                style=wx.SL_HORIZONTAL)
3175            angSlide.Bind(wx.EVT_SLIDER, OnSlider)
3176            Indx[angSlide.GetId()] = rbData
3177            slideSizer.Add(angSlide,0)           
3178            return slideSizer,angSlide
3179           
3180        def FillRefChoice(rbid,rbData):
3181            choiceIds = [i for i in range(len(rbData['atNames']))]
3182            for seq in rbData['rbSeq']:
3183                for i in seq[3]:
3184                    try:
3185                        choiceIds.remove(i)
3186                    except ValueError:
3187                        pass
3188            rbRef = rbData['rbRef']
3189            for i in range(3):
3190                try:
3191                    choiceIds.remove(rbRef[i])
3192                except ValueError:
3193                    pass
3194            refChoice[rbid] = [choiceIds[:],choiceIds[:],choiceIds[:]]
3195            for i in range(3):
3196                refChoice[rbid][i].append(rbRef[i])
3197                refChoice[rbid][i].sort()
3198               
3199        def OnSelect(event):
3200            global resRBsel
3201            sel = select.GetSelection()
3202            if sel == 0: return # 1st entry is blank
3203            rbname = rbchoice[sel-1]
3204            resRBsel = RBnames[rbname]
3205            wx.CallLater(100,UpdateResidueRB)
3206           
3207        #----- beginning of UpdateResidueRB -----------------------------------------------
3208        AtInfo = data['Residue']['AtInfo']
3209        refChoice = {}
3210        RefObjs = []
3211
3212        GS = ResidueRBDisplay.GetSizer()
3213        if GS: 
3214            try:        #get around a c++ error in wx 4.0; doing is again seems to be OK
3215                GS.Clear(True)
3216            except:
3217                GS.Clear(True)
3218       
3219        RBnames = {}
3220        for rbid in data['RBIds']['Residue']:
3221            RBnames.update({data['Residue'][rbid]['RBname']:rbid,})
3222        if not RBnames:
3223            return
3224        rbchoice = list(RBnames.keys())
3225        if GS: 
3226            ResidueRBSizer = GS
3227        else:
3228            ResidueRBSizer = wx.BoxSizer(wx.VERTICAL)
3229        if len(RBnames) > 1:
3230            selSizer = wx.BoxSizer(wx.HORIZONTAL)
3231            selSizer.Add(wx.StaticText(ResidueRBDisplay,
3232                                label=' Select residue to view:'),0)
3233            rbchoice.sort()
3234            select = wx.ComboBox(ResidueRBDisplay,choices=['']+rbchoice)
3235            select.Bind(wx.EVT_COMBOBOX,OnSelect)
3236            selSizer.Add(select,0)
3237            ResidueRBSizer.Add(selSizer,0)
3238        if resRBsel not in data['RBIds']['Residue']:
3239            resRBsel = RBnames[rbchoice[0]]
3240        rbData = data['Residue'][resRBsel]
3241        FillRefChoice(resRBsel,rbData)
3242        ResidueRBSizer.Add(rbNameSizer(resRBsel,rbData),0)
3243        ResidueRBSizer.Add(rbResidues(resRBsel,rbData),0)
3244        if len(rbData['rbSeq']):
3245            ResidueRBSizer.Add((-1,15),0)
3246            slideSizer,angSlide = SlideSizer()
3247            seqSizer = wx.FlexGridSizer(0,5,4,8)
3248            for lbl in 'Sel','','Bond','Angle','Riding atoms':
3249                seqSizer.Add(wx.StaticText(ResidueRBDisplay,wx.ID_ANY,lbl))
3250            ResidueRBSizer.Add(seqSizer)
3251#            for iSeq,Seq in enumerate(rbData['rbSeq']):
3252#                ResidueRBSizer.Add(SeqSizer(angSlide,resRBsel,iSeq,Seq,rbData['atNames']))
3253            for iSeq,Seq in enumerate(rbData['rbSeq']):
3254                Add2SeqSizer(seqSizer,angSlide,resRBsel,iSeq,Seq,rbData['atNames'])
3255            ResidueRBSizer.Add(slideSizer,)
3256
3257        ResidueRBSizer.Add((5,25),)
3258        ResidueRBSizer.Layout()   
3259        ResidueRBDisplay.SetSizer(ResidueRBSizer,True)
3260        ResidueRBDisplay.SetAutoLayout(True)
3261        Size = ResidueRBSizer.GetMinSize()
3262        ResidueRBDisplay.SetSize(Size)
3263       
3264        Size[0] += 40
3265        Size[1] = max(Size[1],450) + 20
3266        ResidueRB.SetSize(Size)
3267        ResidueRB.SetScrollbars(10,10,Size[0]/10-4,Size[1]/10-1)
3268        G2frame.dataWindow.SendSizeEvent()
3269       
3270        ResidueRBDisplay.Show()
3271       
3272    def SetStatusLine(text):
3273        G2frame.GetStatusBar().SetStatusText(text,1)                                     
3274
3275    G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.RigidBodyMenu)
3276    SetStatusLine('')
3277    UpdateVectorRB()
3278    G2frame.rbBook.Bind(wx.aui.EVT_AUINOTEBOOK_PAGE_CHANGED, OnPageChanged)
3279    wx.CallAfter(OnPageChanged,None)
3280
3281def ShowIsoDistortCalc(G2frame,phase=None):
3282    '''Compute the ISODISTORT mode values from the current coordinates.
3283    Called in response to the (Phase/Atoms tab) AtomCompute or
3284    Constraints/Edit Constr. "Show ISODISTORT modes" menu item, which
3285    should be enabled only when Phase['ISODISTORT'] is defined.
3286    '''
3287    def _onClose(event):
3288        dlg.EndModal(wx.ID_CANCEL)
3289    def fmtHelp(item,fullname):
3290        helptext = "A new variable"
3291        if item[-3]:
3292            helptext += " named "+str(item[-3])
3293        helptext += " is a linear combination of the following parameters:\n"
3294        first = True
3295        for term in item[:-3]:
3296            line = ''
3297            var = str(term[1])
3298            m = term[0]
3299            if first:
3300                first = False
3301                line += ' = '
3302            else:
3303                if m >= 0:
3304                    line += ' + '
3305                else:
3306                    line += ' - '
3307                m = abs(m)
3308            line += '%.3f*%s '%(m,var)
3309            varMean = G2obj.fmtVarDescr(var)
3310            helptext += "\n" + line + " ("+ varMean + ")"
3311        helptext += '\n\nISODISTORT full name: '+str(fullname)
3312        return helptext
3313
3314    Histograms,Phases = G2frame.GetUsedHistogramsAndPhasesfromTree() # init for constraint
3315    isophases = [p for p in Phases if 'ISODISTORT' in Phases[p]]
3316   
3317    if not isophases:
3318        G2frame.ErrorDialog('no ISODISTORT phases',
3319                            'Unexpected error: no ISODISTORT phases')
3320        return
3321    if phase and phase not in isophases:
3322        G2frame.ErrorDialog('not ISODISTORT phase',
3323                            'Unexpected error: selected phase is not an ISODISTORT phase')
3324        print('Unexpected error: selected phase is not an ISODISTORT phase',
3325                  phase,isophases)
3326    elif not phase and len(isophases) == 1:
3327        phase = isophases[0]
3328    elif not phase:
3329        dlg = wx.SingleChoiceDialog(G2frame,'Select phase from ISODISTORT phases',
3330                                        'Select Phase',isophases)
3331        if dlg.ShowModal() == wx.ID_OK:
3332            sel = dlg.GetSelection()
3333            phase = isophases[sel]
3334        else:
3335            return
3336    # if len(data.get('Histograms',[])) == 0:
3337    #     G2frame.ErrorDialog(
3338    #         'No data',
3339    #         'Sorry, this computation requires that a histogram first be added to the phase'
3340    #         )
3341    #     return
3342   
3343    covdata = G2frame.GPXtree.GetItemPyData(
3344        G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'Covariance'))
3345    # make a lookup table for named NewVar Phase constraints
3346    sub = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'Constraints') 
3347    Constraints = G2frame.GPXtree.GetItemPyData(sub)
3348    constDict = {}
3349    for c in Constraints['Phase']:
3350        if c[-1] != 'f' or not c[-3]: continue
3351        constDict[str(c[-3])] = c
3352
3353    parmDict,varyList = G2frame.MakeLSParmDict()
3354
3355    dlg = wx.Dialog(G2frame,wx.ID_ANY,'ISODISTORT mode values',#size=(630,400),
3356                       style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
3357    mainSizer = wx.BoxSizer(wx.VERTICAL)
3358    if 'ISODISTORT' not in Phases[phase]:
3359        G2frame.ErrorDialog('not ISODISTORT phase',
3360                            'Unexpected error: selected phase is not an ISODISTORT phase')
3361        return
3362    data = Phases[phase]
3363    ISO = data['ISODISTORT']
3364    mainSizer.Add(wx.StaticText(dlg,wx.ID_ANY,
3365                                'ISODISTORT mode computation for cordinates in phase '+
3366                                str(data['General'].get('Name'))))
3367    aSizer = wx.BoxSizer(wx.HORIZONTAL)
3368    panel1 = wxscroll.ScrolledPanel(
3369        dlg, wx.ID_ANY,#size=(100,200),
3370        style = wx.TAB_TRAVERSAL|wx.SUNKEN_BORDER)
3371    subSizer1 = wx.FlexGridSizer(cols=2,hgap=5,vgap=2)
3372    panel2 = wxscroll.ScrolledPanel(
3373        dlg, wx.ID_ANY,#size=(100,200),
3374        style = wx.TAB_TRAVERSAL|wx.SUNKEN_BORDER)
3375    subSizer2 = wx.FlexGridSizer(cols=3,hgap=5,vgap=2)
3376    subSizer1.Add(wx.StaticText(panel1,wx.ID_ANY,'Parameter name  '))
3377    subSizer1.Add(wx.StaticText(panel1,wx.ID_ANY,' value'),0,wx.ALIGN_RIGHT)
3378    subSizer2.Add((-1,-1))
3379    subSizer2.Add(wx.StaticText(panel2,wx.ID_ANY,'Mode name  '))
3380    subSizer2.Add(wx.StaticText(panel2,wx.ID_ANY,' value'),0,wx.ALIGN_RIGHT)
3381    # ISODISTORT displacive modes
3382    if 'G2VarList' in ISO:
3383        deltaList = []
3384        for gv,Ilbl in zip(ISO['G2VarList'],ISO['IsoVarList']):
3385            dvar = gv.varname()
3386            var = dvar.replace('::dA','::A')
3387            albl = Ilbl[:Ilbl.rfind('_')]
3388            v = Ilbl[Ilbl.rfind('_')+1:]
3389            pval = ISO['ParentStructure'][albl][['dx','dy','dz'].index(v)]
3390            if var in parmDict:
3391                cval = parmDict[var][0]
3392            else:
3393                dlg.EndModal(wx.ID_CANCEL)
3394                G2frame.ErrorDialog('Atom not found',"No value found for parameter "+str(var))
3395                return
3396            deltaList.append(cval-pval)
3397        modeVals = np.inner(ISO['Var2ModeMatrix'],deltaList)
3398        for lbl,xyz,var,val,norm,G2mode in zip(
3399                ISO['IsoVarList'],deltaList,
3400                ISO['IsoModeList'],modeVals,ISO['NormList'],ISO['G2ModeList'] ):
3401            #GSASIIpath.IPyBreak()
3402            if str(G2mode) in constDict:
3403                ch = G2G.HelpButton(panel2,fmtHelp(constDict[str(G2mode)],var))
3404                subSizer2.Add(ch,0,wx.LEFT|wx.RIGHT|wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER,1)
3405            else:
3406                subSizer2.Add((-1,-1))
3407            subSizer1.Add(wx.StaticText(panel1,wx.ID_ANY,str(lbl)))
3408            try:
3409                value = G2py3.FormatSigFigs(xyz)
3410            except TypeError:
3411                value = str(xyz)           
3412            subSizer1.Add(wx.StaticText(panel1,wx.ID_ANY,value),0,wx.ALIGN_RIGHT)
3413            subSizer2.Add(wx.StaticText(panel2,wx.ID_ANY,str(var)))
3414            try:
3415                value = G2py3.FormatSigFigs(val/norm)
3416                if 'varyList' in covdata:
3417                    if str(G2mode) in covdata['varyList']:
3418                        sig = covdata['sig'][covdata['varyList'].index(str(G2mode))]
3419                        value = G2mth.ValEsd(val/norm,sig/norm)
3420            except TypeError:
3421                value = '?'
3422            subSizer2.Add(wx.StaticText(panel2,wx.ID_ANY,value),0,wx.ALIGN_RIGHT)
3423            #GSASIIpath.IPyBreak()
3424    # ISODISTORT occupancy modes
3425    if 'G2OccVarList' in ISO:
3426        deltaList = []
3427        for gv,Ilbl in zip(ISO['G2OccVarList'],ISO['OccVarList']):
3428            var = gv.varname()
3429            albl = Ilbl[:Ilbl.rfind('_')]
3430            pval = ISO['BaseOcc'][albl]
3431            if var in parmDict:
3432                cval = parmDict[var][0]
3433            else:
3434                dlg.EndModal(wx.ID_CANCEL)
3435                G2frame.ErrorDialog('Atom not found',"No value found for parameter "+str(var))
3436                return
3437            deltaList.append(cval-pval)
3438        modeVals = np.inner(ISO['Var2OccMatrix'],deltaList)
3439        for lbl,delocc,var,val,norm,G2mode in zip(
3440                ISO['OccVarList'],deltaList,
3441                ISO['OccModeList'],modeVals,ISO['OccNormList'],ISO['G2OccModeList']):
3442            if str(G2mode) in constDict:
3443                ch = G2G.HelpButton(panel2,fmtHelp(constDict[str(G2mode)],var))
3444                subSizer2.Add(ch,0,wx.LEFT|wx.RIGHT|wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER,1)
3445            else:
3446                subSizer2.Add((-1,-1))
3447            subSizer1.Add(wx.StaticText(panel1,wx.ID_ANY,str(lbl)))
3448            try:
3449                value = G2py3.FormatSigFigs(delocc)
3450            except TypeError:
3451                value = str(delocc)
3452            subSizer1.Add(wx.StaticText(panel1,wx.ID_ANY,value),0,wx.ALIGN_RIGHT)
3453            #subSizer.Add((10,-1))
3454            subSizer2.Add(wx.StaticText(panel2,wx.ID_ANY,str(var)))
3455            try:
3456                value = G2py3.FormatSigFigs(val/norm)
3457                if 'varyList' in covdata:
3458                    if str(G2mode) in covdata['varyList']:
3459                        sig = covdata['sig'][covdata['varyList'].index(str(G2mode))]
3460                        value = G2mth.ValEsd(val/norm,sig/norm)
3461            except TypeError:
3462                value = '?'
3463            subSizer2.Add(wx.StaticText(panel2,wx.ID_ANY,value),0,wx.ALIGN_RIGHT)
3464
3465    # finish up ScrolledPanel
3466    panel1.SetSizer(subSizer1)
3467    panel2.SetSizer(subSizer2)
3468    panel1.SetAutoLayout(1)
3469    panel1.SetupScrolling()
3470    panel2.SetAutoLayout(1)
3471    panel2.SetupScrolling()
3472    # Allow window to be enlarged but not made smaller
3473    dlg.SetSizer(mainSizer)
3474    w1,l1 = subSizer1.GetSize()
3475    w2,l2 = subSizer2.GetSize()
3476    panel1.SetMinSize((w1+10,200))
3477    panel2.SetMinSize((w2+20,200))
3478    aSizer.Add(panel1,1, wx.ALL|wx.EXPAND,1)
3479    aSizer.Add(panel2,2, wx.ALL|wx.EXPAND,1)
3480    mainSizer.Add(aSizer,1, wx.ALL|wx.EXPAND,1)
3481
3482    # make OK button
3483    btnsizer = wx.BoxSizer(wx.HORIZONTAL)
3484    btn = wx.Button(dlg, wx.ID_CLOSE) 
3485    btn.Bind(wx.EVT_BUTTON,_onClose)
3486    btnsizer.Add(btn)
3487    mainSizer.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5)
3488
3489    mainSizer.Fit(dlg)
3490    dlg.SetMinSize(dlg.GetSize())
3491    dlg.ShowModal()
3492    dlg.Destroy()
Note: See TracBrowser for help on using the repository browser.