source: trunk/GSASIIconstrGUI.py @ 4672

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

remove all wx.ALIGN from wx Add with wx.EXPAND - invalid combination ignored in older wx; now flagged in current wx 4.1.x
make ax as self.ax in Absorb.py - fix matplotlib warning about reusing a subaxis
put floors on pink beam alp & bet. Fix typo in GetPinkAlpBet? function - now works better.

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