source: trunk/GSASIIconstrGUI.py @ 4576

Last change on this file since 4576 was 4576, checked in by vondreele, 16 months ago

fix C++ crash in GSASIIconstrGUI.py - 2 OnPageChanged? routines cor constraints & rigid bodies. If event is None just return avoids the crash.

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