source: trunk/GSASIIconstrGUI.py @ 4503

Last change on this file since 4503 was 4503, checked in by toby, 2 years ago

fix problems wit multiple instances of a single RB and with adding atoms via a RB; do not allow an atom to be used in 2 RBs; after SaveAs? put name into recents; fix editing in Residue RB table

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