source: trunk/GSASIIconstrGUI.py @ 4615

Last change on this file since 4615 was 4615, checked in by toby, 3 years ago

Add DrawAtoms_default config option to set default drawing type; add enumeration option for config vars

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