source: trunk/GSASIIconstrGUI.py @ 5065

Last change on this file since 5065 was 5065, checked in by vondreele, 8 months ago

remove check on Histograms & Phases in beginning of UpdateConstraints? - had prevented viewing constraints when no phases
Always enable SHOWISO - same reason
remove check on length of varyList in LoadParmDict? - prevented export of cif when there was no refinement
checks on failed ISODISTORT runs - now shows resulting html page with error message
fixes to RMCProfile GUI startup
fix distortion mode plotting - now moves bonds & polyhedra
cif importer looks for space group number if symbol not interpretable - i.e. full HM symbol
remove a check on k==0 in line 993 of cif importer; k is always zero in ISODISTORT cifs when there in no preset mode distortion value
fix name bug in line 1008 & remove breakpoint in cif importer
ISODISTORT makes html page & displays it in case of ISODISTORT error

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