source: trunk/GSASIIconstrGUI.py @ 4782

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

scripting fix; wx4.1 RB fixes

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