source: trunk/GSASIIconstrGUI.py

Last change on this file was 5309, checked in by vondreele, 3 months ago

Add EFtable to list of returned items from G2strIO.GetPhaseData? every where - I had missed a few adding SEC data type
A little progress on Cluster Analysis

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