source: trunk/GSASIIrestrGUI.py @ 4914

Last change on this file since 4914 was 4914, checked in by toby, 5 months ago

ignore & highlight bad General Restraints; minor GUI fixes

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Author Revision URL Id
File size: 106.5 KB
Line 
1# -*- coding: utf-8 -*-
2#GSASIIrestr - restraint GUI routines
3########### SVN repository information ###################
4# $Date: 2021-05-25 19:37:39 +0000 (Tue, 25 May 2021) $
5# $Author: toby $
6# $Revision: 4914 $
7# $URL: trunk/GSASIIrestrGUI.py $
8# $Id: GSASIIrestrGUI.py 4914 2021-05-25 19:37:39Z toby $
9########### SVN repository information ###################
10'''
11*GSASIIrestrGUI: Restraint GUI routines*
12----------------------------------------
13
14Used to define restraints.
15
16'''
17from __future__ import division, print_function
18import wx
19import wx.grid as wg
20import numpy as np
21import numpy.ma as ma
22import os.path
23import GSASIIpath
24GSASIIpath.SetVersionNumber("$Revision: 4914 $")
25import GSASIImath as G2mth
26import GSASIIlattice as G2lat
27import GSASIIspc as G2spc
28import GSASIIdataGUI as G2gd
29import GSASIIplot as G2plt
30import GSASIIdata as G2data
31import GSASIIctrlGUI as G2G
32import GSASIIphsGUI as G2phsGUI
33import GSASIIobj as G2obj
34import GSASIIconstrGUI as G2cnstG
35import GSASIIexprGUI as G2exG
36
37try:
38    wx.NewIdRef
39    wx.NewId = wx.NewIdRef
40except AttributeError:
41    pass
42
43WACV = wx.ALIGN_CENTER_VERTICAL
44VERY_LIGHT_GREY = wx.Colour(235,235,235)
45VERY_YELLOW = wx.Colour(255,255,0)
46TabSelectionIdDict = {}
47
48################################################################################
49#####  Restraints
50################################################################################           
51def GetSelectedRows(widget,lbl='edit',G2frame=None):
52    '''Returns a list of selected rows. Rows can be selected, blocks of cells
53    or individual cells can be selected. The column for selected cells is ignored.
54    '''
55    try:
56        rows = widget.GetSelectedRows()
57        if rows: return rows
58           
59        top = widget.GetSelectionBlockTopLeft()
60        bot = widget.GetSelectionBlockBottomRight()
61        if top and bot:
62            rows = range(top[0][0],bot[0][0]+1)
63            if rows: return rows
64
65        rows = sorted(list(set([cell[0] for cell in widget.GetSelectedCells()])))
66        if rows: return rows
67
68        choices = ["{}: {}".format(widget.GetRowLabelValue(i),widget.GetCellValue(i,0)) 
69                       for i in range(widget.GetNumberRows())]
70        try:
71            dlg = G2G.G2MultiChoiceDialog(G2frame,'Restraints to '+lbl,
72                                                  'Select restraints',choices)
73            if dlg.ShowModal() != wx.ID_OK: return
74            return dlg.GetSelections()
75        finally:
76            dlg.Destroy()
77    except:
78        return
79   
80def UpdateRestraints(G2frame,data,phaseName):
81    '''Respond to selection of the Restraints item on the
82    data tree
83    '''
84#    global Pages
85   
86    def getMacroFile(macName):
87        defDir = os.path.join(os.path.split(__file__)[0],'GSASIImacros')
88        dlg = wx.FileDialog(G2frame,message='Choose '+macName+' restraint macro file',
89            defaultDir=defDir,defaultFile="",wildcard="GSAS-II macro file (*.mac)|*.mac",
90            style=wx.FD_OPEN | wx.FD_CHANGE_DIR)
91        try:
92            macro = ''
93            if dlg.ShowModal() == wx.ID_OK:
94                macfile = dlg.GetPath()
95                macro = open(macfile,'r')
96                head = macro.readline()
97                if macName not in head:
98                    print (head)
99                    print ('**** ERROR - wrong restraint macro file selected, try again ****')
100                    macro = []
101        finally:
102            dlg.Destroy()
103        return macro        #advanced past 1st line
104       
105    def getMOGULFile():
106        dlg = wx.FileDialog(G2frame,message='Choose MOGUL csv file',
107            defaultDir='.',defaultFile="",wildcard="MOGUL csv file (*.csv)|*.csv",
108            style=wx.FD_OPEN | wx.FD_CHANGE_DIR)
109        try:
110            mogul = ''
111            if dlg.ShowModal() == wx.ID_OK:
112                csvfile = dlg.GetPath()
113                mogul = open(csvfile,'r')
114                head = mogul.readline()
115                if 'Type' not in head:
116                    print (head)
117                    print ('**** ERROR - not a MOGUL csv file selected, try again ****')
118                    mogul = []
119        finally:
120            dlg.Destroy()
121        return mogul        #advanced past 1st line
122       
123    def OnPlotAARestraint(event):
124        page = G2frame.restrBook.GetSelection()
125        if 'Torsion' in G2frame.restrBook.GetPageText(page):
126            torNames = []
127            torNames += list(restrData['Torsion']['Coeff'].keys())
128            dlg = wx.SingleChoiceDialog(G2frame,'Select','Torsion data',torNames)
129            try:
130                if dlg.ShowModal() == wx.ID_OK:
131                    torName = torNames[dlg.GetSelection()]
132                    torsion = G2data.torsionDist[torName]
133                    torCoeff = restrData['Torsion']['Coeff'][torName]
134                    torList = restrData['Torsion']['Torsions']
135                    Names = []
136                    Angles = []
137                    for i,[indx,ops,cofName,esd] in enumerate(torList):
138                        if cofName == torName:
139                            atoms = G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,0,4)
140                            name = '('+atoms[2][1]+atoms[2][0].strip()+atoms[2][2]+')'
141                            for atom in atoms:
142                                name += '  '+atom[3]
143                            XYZ = np.array(G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,cx,3))
144                            angle = G2mth.getRestTorsion(XYZ,Amat)
145                            Angles.append(angle)
146                            Names.append(name) 
147                    G2plt.PlotTorsion(G2frame,phaseName,torsion,torName,Names,np.array(Angles),torCoeff)
148            finally:
149                dlg.Destroy()
150           
151        elif 'Rama' in G2frame.restrBook.GetPageText(page):
152            ramaNames = ['All',]
153            ramaNames += list(restrData['Rama']['Coeff'].keys())
154            dlg = wx.SingleChoiceDialog(G2frame,'Select','Ramachandran data',ramaNames)
155            try:
156                if dlg.ShowModal() == wx.ID_OK:
157                    ramaName = ramaNames[dlg.GetSelection()]
158                    rama = G2data.ramachandranDist[ramaName]
159                    ramaCoeff = []
160                    if ramaName != 'All':
161                        ramaCoeff = restrData['Rama']['Coeff'][ramaName]
162                    ramaList = restrData['Rama']['Ramas']
163                    Names = []
164                    PhiPsi = []
165                    for i,[indx,ops,cofName,esd] in enumerate(ramaList):
166                        if cofName == ramaName or (ramaName == 'All' and '-1' in cofName):
167                            atoms = G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,0,4)
168                            name = '('+atoms[3][1]+atoms[3][0].strip()+atoms[3][2]+')'
169                            for atom in atoms:
170                                name += '  '+atom[3]
171                            XYZ = np.array(G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,cx,3))
172                            phi,psi = G2mth.getRestRama(XYZ,Amat)
173                            PhiPsi.append([phi,psi])
174                            Names.append(name) 
175                    G2plt.PlotRama(G2frame,phaseName,rama,ramaName,Names,np.array(PhiPsi),ramaCoeff)
176            finally:
177                dlg.Destroy()
178
179    def SetupParmDict(G2frame):
180        '''Creates a parameter dict with variable names as keys and
181        numerical values (only)
182        '''
183        G2cnstG.CheckAllScalePhaseFractions(G2frame)
184        try:
185            parmDict,varyList = G2frame.MakeLSParmDict()
186        except:
187            print('Error retrieving parameters')
188            return {}
189        return {i:parmDict[i][0] for i in parmDict}
190       
191    def OnAddRestraint(event):
192        '''Adds a restraint depending on which tab is currently displayed'''
193        page = G2frame.restrBook.GetSelection()
194        if 'Bond' in G2frame.restrBook.GetPageText(page):
195            AddBondRestraint(restrData['Bond'])
196        elif 'Angle' in G2frame.restrBook.GetPageText(page):
197            AddAngleRestraint(restrData['Angle'])
198        elif 'Plane' in G2frame.restrBook.GetPageText(page):
199            AddPlaneRestraint(restrData['Plane'])
200        elif 'Chem' in G2frame.restrBook.GetPageText(page):
201            AddChemCompRestraint(restrData['ChemComp'])
202        elif 'Texture' in G2frame.restrBook.GetPageText(page):
203            AddTextureRestraint(restrData['Texture'])
204        elif 'General' in G2frame.restrBook.GetPageText(page):
205            parmDict = SetupParmDict(G2frame)
206            dlg = G2exG.ExpressionDialog(G2frame,parmDict,
207                           header="Create a restraint expression",
208                           fit=False,wildCard=G2frame.testSeqRefineMode())
209            restobj = dlg.Show(True)
210            if restobj:
211                restrData['General']['General'].append([restobj,0.0,1.0])
212                wx.CallAfter(UpdateGeneralRestr,restrData['General'])
213           
214    def OnAddAARestraint(event):
215        page = G2frame.restrBook.GetSelection()
216        if 'Bond' in G2frame.restrBook.GetPageText(page):
217            AddAABondRestraint(restrData['Bond'])
218        elif 'Angle' in G2frame.restrBook.GetPageText(page):
219            AddAAAngleRestraint(restrData['Angle'])
220        elif 'Plane' in G2frame.restrBook.GetPageText(page):
221            AddAAPlaneRestraint(restrData['Plane'])
222        elif 'Chiral' in G2frame.restrBook.GetPageText(page):
223            AddAAChiralRestraint(restrData['Chiral'])
224        elif 'Torsion' in G2frame.restrBook.GetPageText(page):
225            AddAATorsionRestraint(restrData['Torsion'])
226        elif 'Rama' in G2frame.restrBook.GetPageText(page):
227            AddAARamaRestraint(restrData['Rama'])
228           
229    def OnUseMogul(event):
230        page = G2frame.restrBook.GetSelection()
231        if 'Bond' in G2frame.restrBook.GetPageText(page):
232            AddMogulBondRestraint(restrData['Bond'])
233        elif 'Angle' in G2frame.restrBook.GetPageText(page):
234            AddMogulAngleRestraint(restrData['Angle'])
235           
236    def makeChains(Names,Ids):
237        Chains = {}
238        atoms = zip(Names,Ids)
239        for name,Id in atoms:
240            items = name.split(' ',2)
241            rnum,res = items[0].split(':')
242            rnum = int(rnum)
243            if items[1] not in Chains:
244                Residues = {}
245                Chains[items[1]] = Residues
246            if rnum not in Residues:
247                Residues[rnum] = [[],[]]
248            if items[2][3] in [' ','A']:
249                Residues[rnum][0].append([res,items[2],Id])
250            if items[2][3] in [' ','B']:
251                Residues[rnum][1].append([res,items[2],Id])
252        return Chains
253       
254    def AddBondRestraint(bondRestData):
255        Lists = {'origin':[],'target':[]}
256        for listName in ['origin','target']:
257            dlg = G2G.G2MultiChoiceDialog(G2frame,'Bond restraint '+listName+' for '+General['Name'],
258                    'Select bond restraint '+listName+' atoms',Names)
259            if dlg.ShowModal() == wx.ID_OK:
260                sel = dlg.GetSelections()
261                for x in sel:
262                    if 'all' in Names[x]:
263                        allType = Types[x]
264                        for name,Type,coords,Id in zip(Names,Types,Coords,Ids):
265                            if Type == allType and 'all' not in name:
266                                Lists[listName].append([Id,Type,coords])
267                    else:
268                        Lists[listName].append([Ids[x],Types[x],Coords[x],])
269            else:
270                break
271        if len(Lists['origin']) and len(Lists['target']):
272            bond = 1.54
273            dlg = G2G.SingleFloatDialog(G2frame,'Distance','Enter restraint distance for bond',bond,[0.01,4.],'%.4f')
274            if dlg.ShowModal() == wx.ID_OK:
275                bond = dlg.GetValue()
276            dlg.Destroy()
277        Factor = bondRestData['Range']
278        indices = (-2,-1,0,1,2)
279        Units = np.array([[h,k,l] for h in indices for k in indices for l in indices])
280        origAtoms = Lists['origin']
281        targAtoms = Lists['target']
282        dlg = wx.ProgressDialog("Generating bond restraints","Processed origin atoms",len(origAtoms), 
283            style = wx.PD_ELAPSED_TIME|wx.PD_AUTO_HIDE|wx.PD_REMAINING_TIME)
284        try:
285            Norig = 0
286            for Oid,Otype,Ocoord in origAtoms:
287                Norig += 1
288                dlg.Update(Norig)
289                for Tid,Ttype,Tcoord in targAtoms:
290                    if 'macro' in General['Type']:
291                        result = [[Tcoord,1,[0,0,0],[]],]
292                    else:
293                        result = G2spc.GenAtom(Tcoord,SGData,False,Move=False)
294                    for Txyz,Top,Tunit,Spn in result:
295                        Dx = (Txyz-np.array(Ocoord))+Units
296                        dx = np.inner(Amat,Dx)
297                        dist = ma.masked_less(np.sqrt(np.sum(dx**2,axis=0)),bond/Factor)
298                        IndB = ma.nonzero(ma.masked_greater(dist,bond*Factor))
299                        if np.any(IndB):
300                            for indb in IndB:
301                                for i in range(len(indb)):
302                                    unit = Units[indb][i]+Tunit
303                                    if np.any(unit):
304                                        Topstr = '%d+%d,%d,%d'%(Top,unit[0],unit[1],unit[2])
305                                    else:
306                                        Topstr = str(Top)
307                                    newBond = [[Oid,Tid],['1',Topstr],bond,0.01]
308                                    if newBond not in bondRestData['Bonds']:
309                                        bondRestData['Bonds'].append(newBond)
310        finally:
311            dlg.Destroy()
312        UpdateBondRestr(bondRestData)               
313
314    def AddAABondRestraint(bondRestData):
315        macro = getMacroFile('bond')
316        if not macro:
317            return
318        macStr = macro.readline()
319        atoms = zip(Names,Coords,Ids)
320        Factor = bondRestData['Range']
321        while macStr:
322            items = macStr.split()
323            if 'F' in items[0]:
324                restrData['Bond']['wtFactor'] = float(items[1])
325            elif 'S' in items[0]:
326                oIds = []
327                oCoords = []
328                oDis = []
329                tIds = []
330                tCoords = []
331                tDis = []
332                res = items[1]
333                dist = float(items[2])
334                esd = float(items[3])
335                oAtm,tAtm = items[4:6]
336                for Name,coords,Id in atoms:
337                    names = Name.split(' ',2)
338                    if res == '*' or res in names[0]:
339                        if oAtm.ljust(3) == names[2][:3]:
340                            oIds.append(Id)
341                            oCoords.append(np.array(coords))
342                            oDis.append(names[2][3])
343                        if tAtm.ljust(3) == names[2][:3]:
344                            tIds.append(Id)
345                            tCoords.append(np.array(coords))
346                            tDis.append(names[2][3])
347                for i,[oId,oCoord,odis] in enumerate(zip(oIds,oCoords,oDis)):
348                    for tId,tCoord,tdis in list(zip(tIds,tCoords,tDis))[i:]:
349                        if odis+tdis in ['AB','BA']:
350                            continue
351                        obsd = np.sqrt(np.sum(np.inner(Amat,tCoord-oCoord)**2))
352                        if dist/Factor < obsd < dist*Factor:
353                            newBond = [[oId,tId],['1','1'],dist,esd]
354                            if newBond not in bondRestData['Bonds']:
355                                bondRestData['Bonds'].append(newBond)             
356            macStr = macro.readline()
357        macro.close()
358        print(' Found %d bond restraints'%len(bondRestData['Bonds']))
359        UpdateBondRestr(bondRestData)
360
361    def AddMogulBondRestraint(bondRestData):
362        mogul = getMOGULFile()
363        for line in mogul:
364            items = line.split(',')
365            if 'bond' == items[0]:
366                oName,tName = items[2].split()
367                oInd = Names.index(oName)
368                tInd = Names.index(tName)
369                if items[3] != 'No hits':
370                    dist = float(items[6])
371                    esd = float(items[7])
372                else:
373                    dist = float(items[5])
374                    esd = 0.02
375                newBond = [[Ids[oInd],Ids[tInd]],['1','1'],dist,esd]
376                if newBond not in bondRestData['Bonds']:
377                    bondRestData['Bonds'].append(newBond)             
378        UpdateBondRestr(bondRestData)
379           
380    def AddAngleRestraint(angleRestData):
381        Radii = dict(zip(General['AtomTypes'],zip(General['BondRadii'],General['AngleRadii'])))
382        Lists = {'A-atom':[],'B-atom':[],'C-atom':[]}
383        for listName in ['A-atom','B-atom']:
384            dlg = G2G.G2MultiChoiceDialog(G2frame,'Select '+listName+' for angle A-B-C for '+General['Name']                                                                           ,
385                    'Select angle restraint '+listName,Names)
386            if dlg.ShowModal() == wx.ID_OK:
387                sel = dlg.GetSelections()
388                for x in sel:
389                    if 'all' in Names[x]:
390                        allType = Types[x]
391                        for name,Type,coords,Id in zip(Names,Types,Coords,Ids):
392                            if Type == allType and 'all' not in name:
393                                if 'A' in listName:
394                                    Lists[listName].append(Type)
395                                else:
396                                    Lists[listName].append([Id,Type,coords])
397                    else:
398                        if 'A' in listName:
399                            Lists[listName].append(Types[x])
400                        else:
401                            Lists[listName].append([Ids[x],Types[x],Coords[x],])
402            else:
403                break
404            targAtoms = [[Ids[x+iBeg],Types[x+iBeg],Coords[x+iBeg]] for x in range(len(Names[iBeg:]))]
405        if len(Lists['B-atom']):
406            value = 109.54
407            dlg = G2G.SingleFloatDialog(G2frame,'Angle','Enter restraint angle ',value,[30.,180.],'%.2f')
408            if dlg.ShowModal() == wx.ID_OK:
409                value = dlg.GetValue()
410            dlg.Destroy()
411
412        Factor = angleRestData['Range']
413        indices = (-2,-1,0,1,2)
414        Units = np.array([[h,k,l] for h in indices for k in indices for l in indices])
415        VectA = []
416        for Oid,Otype,Ocoord in Lists['B-atom']:
417            IndBlist = []
418            VectB = []
419            for Tid,Ttype,Tcoord in targAtoms:
420                result = G2spc.GenAtom(Tcoord,SGData,False,Move=False)
421                BsumR = (Radii[Otype][0]+Radii[Ttype][0])*Factor
422                AsumR = (Radii[Otype][1]+Radii[Ttype][1])*Factor
423                for Txyz,Top,Tunit,Spn in result:
424                    Dx = (Txyz-Ocoord)+Units
425                    dx = np.inner(Amat,Dx)
426                    dist = ma.masked_less(np.sqrt(np.sum(dx**2,axis=0)),0.5)
427                    IndB = ma.nonzero(ma.masked_greater(dist,BsumR))
428                    if np.any(IndB):
429                        for indb in IndB:
430                            for i in range(len(indb)):
431                                if str(dx.T[indb][i]) not in IndBlist:
432                                    IndBlist.append(str(dx.T[indb][i]))
433                                    unit = Units[indb][i]+Tunit
434                                if np.any(unit):
435                                    Topstr = '%d+%d,%d,%d'%(Top,unit[0],unit[1],unit[2])
436                                else:
437                                    Topstr = str(Top)
438                                Dist = ma.getdata(dist[indb])[i]
439                                if (Dist-AsumR) <= 0.:
440                                    VectB.append([Oid,'1',Ocoord,Ttype,Tid,Topstr,Tcoord,Dist])
441            VectA.append(VectB)
442        for Vects in VectA:
443            for i,vecta in enumerate(Vects):                   
444                for vectb in Vects[:i]:
445                    if vecta[3] in Lists['A-atom']:
446                        ids = [vecta[4],vecta[0],vectb[4]]
447                        ops = [vecta[5],vecta[1],vectb[5]]
448                        angle = [ids,ops,value,1.0]
449                        if angle not in angleRestData['Angles']:
450                            angleRestData['Angles'].append(angle)
451        UpdateAngleRestr(angleRestData)               
452
453    def AddAAAngleRestraint(angleRestData):
454        macro = getMacroFile('angle')
455        if not macro:
456            return
457        Chains = makeChains(Names,Ids)           
458        macStr = macro.readline()
459        while macStr:
460            items = macStr.split()
461            if 'F' in items[0]:
462                restrData['Angle']['wtFactor'] = float(items[1])
463            elif 'S' in items[0]:
464                Res = items[1]
465                Value = float(items[2])
466                Esd = float(items[3])
467                Atms = items[4:7]
468                pAtms = ['','','']
469                for i,atm in enumerate(Atms):
470                    if '+' in atm:
471                        pAtms[i] = atm.strip('+')
472                ids = [0,0,0]
473                chains = list(Chains.keys())
474                chains.sort()
475                for chain in chains:
476                    residues = list(Chains[chain].keys())
477                    residues.sort()
478                    for residue in residues:
479                        for ires in [0,1]:
480                            if Res != '*':  #works with disordered res
481                                for res,name,Id in Chains[chain][residue][ires]:
482                                    if Res == res:
483                                        try:
484                                            ipos = Atms.index(name[:3].strip())
485                                            ids[ipos] = Id
486                                        except ValueError:
487                                            continue
488                            else:
489                                try:
490                                    for res,name,Id in Chains[chain][residue][ires]:
491                                        try:
492                                            ipos = Atms.index(name[:3].strip())
493                                            ids[ipos] = Id
494                                        except ValueError:
495                                            continue
496                                    for res,name,Id in Chains[chain][residue+1][ires]:
497                                        try:
498                                            ipos = pAtms.index(name[:3].strip())
499                                            ids[ipos] = Id
500                                        except ValueError:
501                                            continue
502                                except KeyError:
503                                    continue
504                            if all(ids):
505                                angle = [list(ids),['1','1','1'],Value,Esd]
506                                if angle not in angleRestData['Angles']:
507                                    angleRestData['Angles'].append(angle)
508                                ids = [0,0,0]
509            macStr = macro.readline()
510        macro.close()
511        print(' Found %d angle restraints'%len(angleRestData['Angles']))
512        UpdateAngleRestr(angleRestData)               
513       
514    def AddMogulAngleRestraint(angleRestData):
515        mogul = getMOGULFile()
516        for line in mogul:
517            items = line.split(',')
518            if 'angle' == items[0]:
519                aName,bName,cName = items[2].split()
520                aInd = Names.index(aName)
521                bInd = Names.index(bName)
522                cInd = Names.index(cName)
523                if items[3] != 'No hits':
524                    angle = float(items[6])
525                    esd = float(items[7])
526                else:
527                    angle = float(items[5])
528                    esd = 2.00
529                newAngle = [[Ids[aInd],Ids[bInd],Ids[cInd]],['1','1','1'],angle,esd]
530                if newAngle not in angleRestData['Angles']:
531                    angleRestData['Angles'].append(newAngle)             
532        UpdateAngleRestr(angleRestData)               
533
534    def AddPlaneRestraint(restrData):
535        ids = []
536        dlg = G2G.G2MultiChoiceDialog(G2frame,'Select 4 or more atoms for plane in '+General['Name'],
537                'Select 4+ atoms',Names[iBeg:])
538        if dlg.ShowModal() == wx.ID_OK:
539            sel = dlg.GetSelections()
540            if len(sel) > 3:
541                for x in sel:
542                    ids.append(Ids[x+iBeg])
543                ops = ['1' for i in range(len(sel))]
544                plane = [ids,ops,0.0,0.01]
545                if plane not in restrData['Planes']:
546                    restrData['Planes'].append(plane)
547            else:
548                print ('**** ERROR - not enough atoms for a plane restraint - try again ****')
549        UpdatePlaneRestr(restrData)               
550
551    def AddAAPlaneRestraint(planeRestData):
552        macro = getMacroFile('plane')
553        if not macro:
554            return
555        Chains = makeChains(Names,Ids)           
556        macStr = macro.readline()
557        while macStr:
558            items = macStr.split()
559            if 'F' in items[0]:
560                restrData['Plane']['wtFactor'] = float(items[1])
561            elif 'S' in items[0]:
562                Res = items[1]
563                Esd = float(items[2])
564                Atms = items[3:]
565                pAtms = ['' for i in Atms]
566                for i,atm in enumerate(Atms):
567                    if '+' in atm:
568                        pAtms[i] = atm.strip('+')
569                ids = [0,]*len(Atms)
570                ops = ['1' for i in range(len(Atms))]
571                chains = list(Chains.keys())
572                chains.sort()
573                for chain in chains:
574                    residues = list(Chains[chain].keys())
575                    residues.sort()
576                    for residue in residues:
577                        for ires in [0,1]:
578                            if residue == residues[-1] and Res == '*':
579                                continue
580                            if Res != '*':  #works with disordered res
581                                for res,name,Id in Chains[chain][residue][ires]:
582                                    if Res == res:
583                                        try:
584                                            ipos = Atms.index(name[:3].strip())
585                                            ids[ipos] = Id
586                                        except ValueError:
587                                            continue
588                            else:
589                                try:
590                                    for res,name,Id in Chains[chain][residue][ires]:
591                                        try:
592                                            ipos = Atms.index(name[:3].strip())
593                                            ids[ipos] = Id
594                                        except ValueError:
595                                            continue
596                                    for res,name,Id in Chains[chain][residue+1][ires]:
597                                        try:
598                                            ipos = pAtms.index(name[:3].strip())
599                                            ids[ipos] = Id
600                                        except ValueError:
601                                            continue
602                                except KeyError:
603                                    continue
604                            if all(ids):
605                                plane = [list(ids),ops,0.0,Esd]
606                                if plane not in planeRestData['Planes']:
607                                    planeRestData['Planes'].append(plane)
608                                ids = [0,]*len(Atms)
609            macStr = macro.readline()
610        macro.close()
611        print(' Found %d plane restraints'%len(planeRestData['Planes']))
612        UpdatePlaneRestr(planeRestData)               
613
614    def AddAAChiralRestraint(chiralRestData):
615        macro = getMacroFile('chiral')
616        if not macro:
617            return
618        Chains = makeChains(Names,Ids)           
619        macStr = macro.readline()
620        while macStr:
621            items = macStr.split()
622            if 'F' in items[0]:
623                restrData['Chiral']['wtFactor'] = float(items[1])
624            elif 'S' in items[0]:
625                Res = items[1]
626                Value = float(items[2])
627                Esd = float(items[3])
628                Atms = items[4:8]
629                ids = [0,0,0,0]
630                chains = list(Chains.keys())
631                chains.sort()
632                for chain in chains:
633                    residues = list(Chains[chain].keys())
634                    residues.sort()
635                    for residue in residues:
636                        for ires in [0,1]:
637                            if residue == residues[-1] and Res == '*':
638                                continue
639                            if Res != '*':  #works with disordered res
640                                for res,name,Id in Chains[chain][residue][ires]:
641                                    if Res == res:
642                                        try:
643                                            ipos = Atms.index(name[:3].strip())
644                                            ids[ipos] = Id
645                                        except ValueError:
646                                            continue
647                            else:
648                                try:
649                                    for res,name,Id in Chains[chain][residue][ires]:
650                                        try:
651                                            ipos = Atms.index(name[:3].strip())
652                                            ids[ipos] = Id
653                                        except ValueError:
654                                            continue
655                                except KeyError:
656                                    continue
657                            if all(ids):
658                                chiral = [list(ids),['1','1','1','1'],Value,Esd]
659                                if chiral not in chiralRestData['Volumes']:
660                                    chiralRestData['Volumes'].append(chiral)
661                                ids = [0,0,0,0]
662            macStr = macro.readline()
663        macro.close()
664        print(' Found %d chiral volumes restraints'%len(chiralRestData['Volumes']))
665        UpdateChiralRestr(chiralRestData)               
666       
667    def AddAATorsionRestraint(torsionRestData):
668        macro = getMacroFile('torsion')
669        if not macro:
670            return
671        Chains = makeChains(Names,Ids)           
672        macStr = macro.readline()[:-1]
673        while macStr:
674            items = macStr.split()
675            if 'F' in items[0]:
676                restrData['Torsion']['wtFactor'] = float(items[1])
677            elif 'A' in items[0]:
678                name = items[10]
679                coeff = np.zeros(9)
680                for i,item in enumerate(items[1:10]):
681                    coeff[i] = float(item)
682                torsionRestData['Coeff'][name] = coeff
683            elif 'S' in items[0]:
684                Name = items[1]
685                Res = items[2]
686                Esd = float(items[3])
687                Atms = items[4:8]
688                pAtms = ['','','','']
689                for i,atm in enumerate(Atms):
690                    if '+' in atm:
691                        pAtms[i] = atm.strip('+')
692                ids = [0,0,0,0]
693                chains = list(Chains.keys())
694                chains.sort()
695                for chain in chains:
696                    residues = list(Chains[chain].keys())
697                    residues.sort()
698                    for residue in residues:
699                        for ires in [0,1]:
700                            if residue == residues[-1] and Res == '*':
701                                continue
702                            if Res != '*':  #works with disordered res
703                                for res,name,Id in Chains[chain][residue][ires]:
704                                    if Res == res:
705                                        try:
706                                            ipos = Atms.index(name[:3].strip())
707                                            ids[ipos] = Id
708                                        except ValueError:
709                                            continue
710                            else:
711                                try:
712                                    for res,name,Id in Chains[chain][residue][ires]:
713                                        try:
714                                            ipos = Atms.index(name[:3].strip())
715                                            ids[ipos] = Id
716                                        except ValueError:
717                                            continue
718                                    for res,name,Id in Chains[chain][residue+1][ires]:
719                                        try:
720                                            ipos = pAtms.index(name[:3].strip())
721                                            ids[ipos] = Id
722                                        except ValueError:
723                                            continue
724                                except KeyError:
725                                    continue
726                            if all(ids):
727                                torsion = [list(ids),['1','1','1','1'],Name,Esd]
728                                if torsion not in torsionRestData['Torsions']:
729                                    torsionRestData['Torsions'].append(torsion)
730                                ids = [0,0,0,0]                           
731            macStr = macro.readline()
732        macro.close()
733        print(' Found %d torsion restraints'%len(torsionRestData['Torsions']))
734        UpdateTorsionRestr(torsionRestData)                       
735       
736    def AddAARamaRestraint(ramaRestData):
737        macro = getMacroFile('Ramachandran')
738        if not macro:
739            return
740        Chains = makeChains(Names,Ids)           
741        macStr = macro.readline()
742        while macStr:
743            items = macStr.split()
744            if 'F' in items[0]:
745                restrData['Rama']['wtFactor'] = float(items[1])
746            elif 'A' in items[0]:
747                nTerms = int(items[1])
748                name = items[2]
749                coeff = np.zeros((nTerms,6))
750                for i in range(nTerms):
751                    macStr = macro.readline()
752                    items = macStr.split()
753                    for j,val in enumerate(items):
754                        coeff[i][j] = float(val)
755                ramaRestData['Coeff'][name] = coeff
756            elif 'S' in items[0]:
757                Name = items[1]
758                Res = items[2]
759                Esd = float(items[3])
760                Atms = items[4:9]
761                mAtms = ['','','','','']
762                pAtms = ['','','','','']
763                for i,atm in enumerate(Atms):
764                    if '+' in atm:
765                        pAtms[i] = atm.strip('+')
766                    elif '-' in atm:
767                        mAtms[i] = atm.strip('-')
768                ids = [0,0,0,0,0]
769                chains = list(Chains.keys())
770                chains.sort()
771                for chain in chains:
772                    residues = list(Chains[chain].keys())
773                    residues.sort()
774                    if not (any(mAtms) or any(pAtms)):
775                        for residue in residues:
776                            for ires in [0,1]:
777                                for res,name,Id in Chains[chain][residue][ires]:
778                                    if Res == res:
779                                        try:
780                                            ipos = Atms.index(name[:3].strip())
781                                            ids[ipos] = Id
782                                        except ValueError:
783                                            continue
784                                if all(ids):
785                                    rama = [list(ids),['1','1','1','1','1'],Name,Esd]
786                                    if rama not in ramaRestData['Ramas']:
787                                        ramaRestData['Ramas'].append(rama)
788                                    ids = [0,0,0,0,0]
789                    else:
790                        for ires,residue in enumerate(residues[1:-1]):
791                            for jres in [0,1]:
792                                try:
793                                    for res,name,Id in Chains[chain][residue-1][jres]:
794                                        try:
795                                            ipos = mAtms.index(name[:3].strip())
796                                            ids[ipos] = Id
797                                        except ValueError:
798                                            continue
799                                    for res,name,Id in Chains[chain][residue+1][jres]:
800                                        try:
801                                            ipos = pAtms.index(name[:3].strip())
802                                            ids[ipos] = Id
803                                        except ValueError:
804                                            continue
805                                    for res,name,Id in Chains[chain][residue][jres]:
806                                        if Res == res:
807                                            try:
808                                                ipos = Atms.index(name[:3].strip())
809                                                ids[ipos] = Id
810                                            except ValueError:
811                                                continue
812                                    if all(ids):
813                                        rama = [list(ids),['1','1','1','1','1'],Name,Esd]
814                                        if rama not in ramaRestData['Ramas']:
815                                            ramaRestData['Ramas'].append(rama)
816                                        ids = [0,0,0,0,0]
817                                except KeyError:
818                                    continue
819            macStr = macro.readline()
820        macro.close()
821        print(' Found %d Ramachandran restraints'%len(ramaRestData['Ramas']))
822        UpdateRamaRestr(ramaRestData)
823       
824    def AddChemCompRestraint(chemcompRestData):
825        ids = []
826        factors = []
827        dlg = G2G.G2MultiChoiceDialog(G2frame,'Select atoms for chemical restraint in '+General['Name'],
828                'Select atoms',Names)
829        if dlg.ShowModal() == wx.ID_OK:
830            sel = dlg.GetSelections()
831            for x in sel:
832                if 'all' in Names[x]:
833                    allType = Types[x]
834                    for name,Type,Id in zip(Names,Types,Ids):
835                        if Type == allType and 'all' not in name:
836                            ids.append(Id)
837                            factors.append(1.0)
838                else:
839                    ids.append(Ids[x])
840                    factors.append(1.0)
841            dlg.Destroy()
842            if len(ids) > 0:
843                value = 1.0
844                dlg = G2G.SingleFloatDialog(G2frame,'Cell sum','Enter unit cell sum ',value,[-1.e6,1.e6],'%.2f')
845                if dlg.ShowModal() == wx.ID_OK:
846                    value = dlg.GetValue()               
847                    comp = [ids,factors,value,0.01]
848                    if comp not in chemcompRestData['Sites']:
849                        chemcompRestData['Sites'].append(comp)
850                UpdateChemcompRestr(chemcompRestData)
851            else:
852                print ('**** ERROR - not enough atoms for a composition restraint - try again ****')
853       
854    def AddTextureRestraint(textureRestData):
855        dlg = wx.TextEntryDialog(G2frame,'Enter h k l for pole figure restraint','Enter HKL','')
856        if dlg.ShowModal() == wx.ID_OK:
857            text = dlg.GetValue()
858            vals = text.split()
859            try:
860                hkl = [int(vals[i]) for i in range(3)]
861                texture = [hkl,24,0.01,False,1.0]
862                if texture not in textureRestData['HKLs']:
863                        textureRestData['HKLs'].append(texture)
864                UpdateTextureRestr(textureRestData)
865            except (ValueError,IndexError):
866                print ('**** ERROR - bad input of h k l - try again ****')
867        dlg.Destroy()
868               
869    def WtBox(wind,restData):
870        if 'Range' not in restData: restData['Range'] = 1.1     #patch
871       
872        def OnUseData(event):
873            Obj = event.GetEventObject()
874            restData['Use'] = Obj.GetValue()
875
876        wtBox = wx.BoxSizer(wx.HORIZONTAL)
877        wtBox.Add(wx.StaticText(wind,-1,'Phase '+phaseName+' Restraint weight factor: '),0,WACV)
878        wtfactor = G2G.ValidatedTxtCtrl(wind,restData,'wtFactor',nDig=(10,2),typeHint=float)
879        wtBox.Add(wtfactor,0,WACV)
880        useData = wx.CheckBox(wind,-1,label=' Use?')
881        useData.Bind(wx.EVT_CHECKBOX, OnUseData)
882        useData.SetValue(restData['Use'])       
883        wtBox.Add(useData,0,WACV)
884        if 'Bonds' in restData or 'Angles' in restData:
885            wtBox.Add(wx.StaticText(wind,-1,'  Search range: '),0,WACV)
886            sRange = G2G.ValidatedTxtCtrl(wind,restData,'Range',nDig=(10,2),typeHint=float)
887            wtBox.Add(sRange,0,WACV)
888            wtBox.Add(wx.StaticText(wind,-1,' x sum(atom radii)'),0,WACV)
889        return wtBox
890       
891    def OnRowSelect(event):
892        r,c =  event.GetRow(),event.GetCol()
893        Obj = event.GetEventObject()
894        if r < 0 and c < 0:
895            if Obj.IsSelection():
896                Obj.ClearSelection()
897            else:
898                for row in range(Obj.GetNumberRows()):
899                    Obj.SelectRow(row,True)
900        elif c < 0:                   #only row clicks
901            if event.ControlDown():                   
902                if r in Obj.GetSelectedRows():
903                    Obj.DeselectRow(r)
904                else:
905                    Obj.SelectRow(r,True)
906            elif event.ShiftDown():
907                indxs = Obj.GetSelectedRows()
908                Obj.ClearSelection()
909                ibeg = 0
910                if indxs:
911                    ibeg = indxs[-1]
912                for row in range(ibeg,r+1):
913                    Obj.SelectRow(row,True)
914            else:
915                Obj.ClearSelection()
916                Obj.SelectRow(r,True)
917               
918                   
919    def UpdateBondRestr(bondRestData):
920       
921        def OnCellChange(event):
922            r,c =  event.GetRow(),event.GetCol()
923            try:
924                new = float(bondTable.GetValue(r,c))
925                if new <= 0.:
926                    raise ValueError
927                bondRestData['Bonds'][r][c] = new
928            except ValueError:
929                pass           
930            wx.CallAfter(UpdateBondRestr,bondRestData)               
931
932        def OnChangeValue(event):
933            rows = GetSelectedRows(Bonds)
934            if not rows:
935                return
936            Bonds.ClearSelection()
937            val = bondList[rows[0]][2]
938            dlg = G2G.SingleFloatDialog(G2frame,'New value','Enter new value for bond',val,[0.,5.],'%.4f')
939            if dlg.ShowModal() == wx.ID_OK:
940                parm = dlg.GetValue()
941                for r in rows:
942                    bondRestData['Bonds'][r][2] = parm
943            dlg.Destroy()
944            UpdateBondRestr(bondRestData)               
945
946        def OnChangeEsd(event):
947            rows = GetSelectedRows(Bonds)
948            if not rows:
949                return
950            Bonds.ClearSelection()
951            val = bondList[rows[0]][3]
952            dlg = G2G.SingleFloatDialog(G2frame,'New value','Enter new esd for bond',val,[0.,1.],'%.4f')
953            if dlg.ShowModal() == wx.ID_OK:
954                parm = dlg.GetValue()
955                for r in rows:
956                    bondRestData['Bonds'][r][3] = parm
957            dlg.Destroy()
958            UpdateBondRestr(bondRestData)               
959                               
960        def OnDeleteRestraint(event):
961            rows = GetSelectedRows(Bonds,'delete',G2frame)
962            G2frame.GetStatusBar().SetStatusText('',1)
963            if not rows:
964                G2frame.GetStatusBar().SetStatusText('First select restraints to be deleted',1)
965                return
966            Bonds.ClearSelection()
967            rows.sort()
968            rows.reverse()
969            for row in rows:
970                bondList.remove(bondList[row])
971            UpdateBondRestr(bondRestData)               
972
973        try:
974            if BondRestr.GetSizer(): BondRestr.GetSizer().Clear(True)
975        except:
976            pass
977        mainSizer = wx.BoxSizer(wx.VERTICAL)
978        mainSizer.Add((5,5),0)
979        mainSizer.Add(WtBox(BondRestr,bondRestData),0)
980
981        for i in (G2G.wxID_RESTDELETE,G2G.wxID_RESRCHANGEVAL,G2G.wxID_RESTCHANGEESD):
982            G2frame.dataWindow.RestraintEdit.Enable(id=i,enable=False)
983        bondList = bondRestData['Bonds']
984        if len(bondList) and len(bondList[0]) == 6:   #patch
985            bondList = bondRestData['Bonds'] = []
986        if len(bondList):
987            table = []
988            rowLabels = []
989            bad = []
990            chisq = 0.
991            Types = [wg.GRID_VALUE_STRING,]+4*[wg.GRID_VALUE_FLOAT+':10,3',]
992            if 'macro' in General['Type']:
993                colLabels = ['(res) A - (res) B','calc','target','esd','delt/sig']
994                for i,[indx,ops,obs,esd] in enumerate(bondList):
995                    try:
996                        atoms = G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,0,4)
997                        name = ''
998                        for atom in atoms:
999                            name += '('+atom[1]+atom[0].strip()+atom[2]+') '+atom[3]+' - '
1000                        XYZ = np.array(G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,cx,3))
1001                        calc = G2mth.getRestDist(XYZ,Amat)
1002                        chisq += bondRestData['wtFactor']*((obs-calc)/esd)**2
1003                        table.append([name[:-3],calc,obs,esd,(obs-calc)/esd])
1004                        rowLabels.append(str(i))               
1005                    except KeyError:
1006                        print ('**** WARNING - missing atom - restraint deleted ****')
1007                        bad.append(i)
1008            else:
1009                colLabels = ['A+SymOp - B+SymOp','calc','target','esd','delt/sig']
1010                for i,[indx,ops,obs,esd] in enumerate(bondList):
1011                    try:
1012                        names = G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,ct-1)
1013                        XYZ = np.array(G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,cx,3))
1014                        XYZ = G2mth.getSyXYZ(XYZ,ops,SGData)
1015                        calc = G2mth.getRestDist(XYZ,Amat)
1016                        chisq += bondRestData['wtFactor']*((obs-calc)/esd)**2
1017                        table.append([names[0]+'+('+ops[0]+') - '+names[1]+'+('+ops[1]+')',calc,obs,esd,(obs-calc)/esd])
1018                        rowLabels.append(str(i))
1019                    except KeyError:
1020                        print ('**** WARNING - missing atom - restraint deleted ****')
1021                        bad.append(i)
1022            if len(bad):
1023                bad.reverse()
1024                for ibad in bad:
1025                    del bondList[ibad]
1026            if len(bondList):
1027                bondTable = G2G.Table(table,rowLabels=rowLabels,colLabels=colLabels,types=Types)
1028                Bonds = G2G.GSGrid(BondRestr)
1029                Bonds.SetTable(bondTable, True)
1030                Bonds.AutoSizeColumns(False)
1031                for c in [0,1,4]: # format read-only rows
1032                    attr = wx.grid.GridCellAttr()
1033                    attr.IncRef()
1034                    attr.SetBackgroundColour(VERY_LIGHT_GREY)
1035                    attr.SetReadOnly(True)
1036                    Bonds.SetColAttr(c, attr)
1037                Bonds.Bind(wg.EVT_GRID_LABEL_LEFT_CLICK,OnRowSelect)
1038                if 'phoenix' in wx.version():
1039                    Bonds.Bind(wg.EVT_GRID_CELL_CHANGED, OnCellChange)
1040                else:
1041                    Bonds.Bind(wg.EVT_GRID_CELL_CHANGE, OnCellChange)
1042                for i in (G2G.wxID_RESTDELETE,G2G.wxID_RESRCHANGEVAL,G2G.wxID_RESTCHANGEESD):
1043                    G2frame.dataWindow.RestraintEdit.Enable(id=i,enable=True)
1044                G2frame.Bind(wx.EVT_MENU, OnDeleteRestraint, id=G2G.wxID_RESTDELETE)
1045                G2frame.Bind(wx.EVT_MENU, OnChangeValue, id=G2G.wxID_RESRCHANGEVAL)
1046                G2frame.Bind(wx.EVT_MENU, OnChangeEsd, id=G2G.wxID_RESTCHANGEESD)
1047                mainSizer.Add(wx.StaticText(BondRestr,-1,
1048                    'Bond restraints: sum(wt*(delt/sig)^2) =    %.2f, mean(wt*(delt/sig)^2) =    %.2f'    \
1049                    %(chisq,chisq/len(bondList))),0)
1050                Bonds.SetScrollRate(10,10)
1051                Bonds.SetMinSize((-1,300))
1052                mainSizer.Add(Bonds,1,wx.EXPAND,1)
1053            else:
1054                mainSizer.Add(wx.StaticText(BondRestr,-1,'No bond distance restraints for this phase'),0,)
1055        else:
1056            mainSizer.Add(wx.StaticText(BondRestr,-1,'No bond distance restraints for this phase'),0,)
1057
1058        wx.CallAfter(G2phsGUI.SetPhaseWindow,BondRestr,mainSizer,Scroll=0)
1059       
1060    def UpdateAngleRestr(angleRestData):
1061       
1062        def OnCellChange(event):
1063            r,c =  event.GetRow(),event.GetCol()
1064            try:
1065                new = float(angleTable.GetValue(r,c))
1066                if new <= 0. or new > 180.:
1067                    raise ValueError
1068                angleRestData['Angles'][r][c] = new
1069            except ValueError:
1070                pass           
1071            wx.CallAfter(UpdateAngleRestr,angleRestData)               
1072           
1073        def OnChangeValue(event):
1074            rows = GetSelectedRows(Angles)
1075            if not rows:
1076                return
1077            Angles.ClearSelection()
1078            val = angleList[rows[0]][2]
1079            dlg = G2G.SingleFloatDialog(G2frame,'New value','Enter new value for angle',val,[0.,360.],'%.2f')
1080            if dlg.ShowModal() == wx.ID_OK:
1081                parm = dlg.GetValue()
1082                for r in rows:
1083                    angleRestData['Angles'][r][2] = parm
1084            dlg.Destroy()
1085            UpdateAngleRestr(angleRestData)               
1086
1087        def OnChangeEsd(event):
1088            rows = GetSelectedRows(Angles)
1089            if not rows:
1090                return
1091            Angles.ClearSelection()
1092            val = angleList[rows[0]][3]
1093            dlg = G2G.SingleFloatDialog(G2frame,'New value','Enter new esd for angle',val,[0.,5.],'%.2f')
1094            if dlg.ShowModal() == wx.ID_OK:
1095                parm = dlg.GetValue()
1096                for r in rows:
1097                    angleRestData['Angles'][r][3] = parm
1098            dlg.Destroy()
1099            UpdateAngleRestr(angleRestData)               
1100                                           
1101        def OnDeleteRestraint(event):
1102            rows = GetSelectedRows(Angles,'delete',G2frame)
1103            G2frame.GetStatusBar().SetStatusText('',1)
1104            if not rows:
1105                G2frame.GetStatusBar().SetStatusText('First select restraints to be deleted',1)
1106                return
1107            rows.sort()
1108            rows.reverse()
1109            for row in rows:
1110                angleList.remove(angleList[row])
1111            UpdateAngleRestr(angleRestData)               
1112           
1113        try:
1114            if AngleRestr.GetSizer():
1115                AngleRestr.GetSizer().Clear(True)
1116        except:
1117            pass
1118        mainSizer = wx.BoxSizer(wx.VERTICAL)
1119        mainSizer.Add((5,5),0)
1120        mainSizer.Add(WtBox(AngleRestr,angleRestData),0)
1121
1122        for i in (G2G.wxID_RESTDELETE,G2G.wxID_RESRCHANGEVAL,G2G.wxID_RESTCHANGEESD):
1123            G2frame.dataWindow.RestraintEdit.Enable(id=i,enable=False)
1124        angleList = angleRestData['Angles']
1125        if len(angleList):
1126            table = []
1127            rowLabels = []
1128            bad = []
1129            chisq = 0.
1130            Types = [wg.GRID_VALUE_STRING,]+4*[wg.GRID_VALUE_FLOAT+':10,2',]
1131            if 'macro' in General['Type']:
1132                colLabels = ['(res) A - (res) B - (res) C','calc','target','esd','delt/sig']
1133                for i,[indx,ops,obs,esd] in enumerate(angleList):
1134                    try:
1135                        atoms = G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,0,4)
1136                        name = ''
1137                        for atom in atoms:
1138                            name += '('+atom[1]+atom[0].strip()+atom[2]+') '+atom[3]+' - '
1139                        XYZ = np.array(G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,cx,3))
1140                        calc = G2mth.getRestAngle(XYZ,Amat)
1141                        chisq += angleRestData['wtFactor']*((obs-calc)/esd)**2
1142                        table.append([name[:-3],calc,obs,esd,(obs-calc)/esd])
1143                        rowLabels.append(str(i))                               
1144                    except KeyError:
1145                        print ('**** WARNING - missing atom - restraint deleted ****')
1146                        bad.append(i)
1147            else:
1148                colLabels = ['A+SymOp - B+SymOp - C+SymOp','calc','target','esd','delt/sig']
1149                for i,[indx,ops,obs,esd] in enumerate(angleList):
1150                    try:
1151                        atoms = G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,ct-1)
1152                        name = atoms[0]+'+('+ops[0]+') - '+atoms[1]+'+('+ops[1]+') - '+atoms[2]+ \
1153                        '+('+ops[2]+')'
1154                        XYZ = np.array(G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,cx,3))
1155                        XYZ = G2mth.getSyXYZ(XYZ,ops,SGData)
1156                        calc = G2mth.getRestAngle(XYZ,Amat)
1157                        chisq += angleRestData['wtFactor']*((obs-calc)/esd)**2
1158                        table.append([name,calc,obs,esd,(obs-calc)/esd])
1159                        rowLabels.append(str(i))
1160                    except KeyError:
1161                        print ('**** WARNING - missing atom - restraint deleted ****')
1162                        bad.append(i)
1163            if len(bad):
1164                bad.reverse()
1165                for ibad in bad:
1166                    del angleList[ibad]
1167            if len(angleList):
1168                angleTable = G2G.Table(table,rowLabels=rowLabels,colLabels=colLabels,types=Types)
1169                Angles = G2G.GSGrid(AngleRestr)
1170                Angles.SetTable(angleTable, True)
1171                Angles.AutoSizeColumns(False)
1172                for c in [0,1,4]: # format readonly columns
1173                    attr = wx.grid.GridCellAttr()
1174                    attr.IncRef()
1175                    attr.SetBackgroundColour(VERY_LIGHT_GREY)
1176                    attr.SetReadOnly(True)
1177                    Angles.SetColAttr(c, attr)
1178                Angles.Bind(wg.EVT_GRID_LABEL_LEFT_CLICK,OnRowSelect)
1179                if 'phoenix' in wx.version():
1180                    Angles.Bind(wg.EVT_GRID_CELL_CHANGED, OnCellChange)
1181                else:
1182                    Angles.Bind(wg.EVT_GRID_CELL_CHANGE, OnCellChange)
1183                for i in (G2G.wxID_RESTDELETE,G2G.wxID_RESRCHANGEVAL,G2G.wxID_RESTCHANGEESD):
1184                    G2frame.dataWindow.RestraintEdit.Enable(id=i,enable=True)
1185                G2frame.Bind(wx.EVT_MENU, OnDeleteRestraint, id=G2G.wxID_RESTDELETE)
1186                G2frame.Bind(wx.EVT_MENU, OnChangeValue, id=G2G.wxID_RESRCHANGEVAL)
1187                G2frame.Bind(wx.EVT_MENU, OnChangeEsd, id=G2G.wxID_RESTCHANGEESD)
1188                mainSizer.Add(wx.StaticText(AngleRestr,-1,
1189                    'Angle restraints: sum(wt*(delt/sig)^2) =    %.2f, mean(wt*(delt/sig)^2) =    %.2f'    \
1190                    %(chisq,chisq/len(angleList))),0)
1191                Angles.SetScrollRate(10,10)
1192                Angles.SetMinSize((-1,300))
1193                mainSizer.Add(Angles,1,wx.EXPAND,1)
1194            else:
1195                mainSizer.Add(wx.StaticText(AngleRestr,-1,'No bond angle restraints for this phase'),0,)
1196        else:
1197            mainSizer.Add(wx.StaticText(AngleRestr,-1,'No bond angle restraints for this phase'),0,)
1198        wx.CallAfter(G2phsGUI.SetPhaseWindow,AngleRestr,mainSizer,Scroll=0)
1199   
1200    def UpdatePlaneRestr(planeRestData):
1201       
1202        items = G2frame.dataWindow.RestraintEdit.GetMenuItems()
1203        for item in items:
1204            if item.GetItemLabelText() in ['Change value']:
1205                item.Enable(False)
1206
1207        def OnCellChange(event):
1208            r,c =  event.GetRow(),event.GetCol()
1209            try:
1210                new = float(planeTable.GetValue(r,c))
1211                if new <= 0.:
1212                    raise ValueError
1213                planeRestData['Planes'][r][c] = new
1214            except ValueError:
1215                pass           
1216            wx.CallAfter(UpdatePlaneRestr,planeRestData)               
1217           
1218        def OnChangeEsd(event):
1219            rows = GetSelectedRows(Planes)
1220            if not rows:
1221                return
1222            Planes.ClearSelection()
1223            val = planeList[rows[0]][3]
1224            dlg = G2G.SingleFloatDialog(G2frame,'New value','Enter new esd for plane',val,[0.,5.],'%.2f')
1225            if dlg.ShowModal() == wx.ID_OK:
1226                parm = dlg.GetValue()
1227                for r in rows:
1228                    planeRestData['Planes'][r][3] = parm
1229            dlg.Destroy()
1230            UpdatePlaneRestr(planeRestData)               
1231                                           
1232        def OnDeleteRestraint(event):
1233            rows = GetSelectedRows(Planes,'delete',G2frame)
1234            G2frame.GetStatusBar().SetStatusText('',1)
1235            if not rows:
1236                G2frame.GetStatusBar().SetStatusText('First select restraints to be deleted',1)
1237                return
1238            rows.sort()
1239            rows.reverse()
1240            for row in rows:
1241                planeList.remove(planeList[row])
1242            UpdatePlaneRestr(planeRestData)               
1243           
1244        try:
1245            if PlaneRestr.GetSizer():
1246                PlaneRestr.GetSizer().Clear(True)
1247        except:
1248            pass
1249        mainSizer = wx.BoxSizer(wx.VERTICAL)
1250        mainSizer.Add((5,5),0)
1251        mainSizer.Add(WtBox(PlaneRestr,planeRestData),0)
1252
1253        for i in (G2G.wxID_RESTDELETE,G2G.wxID_RESRCHANGEVAL,G2G.wxID_RESTCHANGEESD):
1254            G2frame.dataWindow.RestraintEdit.Enable(id=i,enable=False)
1255        planeList = planeRestData['Planes']
1256        if len(planeList):
1257            table = []
1258            rowLabels = []
1259            bad = []
1260            chisq = 0.
1261            Types = [wg.GRID_VALUE_STRING,]+3*[wg.GRID_VALUE_FLOAT+':10,2',]
1262            if 'macro' in General['Type']:
1263                colLabels = ['(res) atom','calc','target','esd']
1264                for i,[indx,ops,obs,esd] in enumerate(planeList):
1265                    try:
1266                        atoms = G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,0,4)
1267                        name = ''
1268                        for a,atom in enumerate(atoms):
1269                            name += '('+atom[1]+atom[0].strip()+atom[2]+') '+atom[3]+' - '
1270                            if (a+1)%3 == 0:
1271                                name += '\n'
1272                        XYZ = np.array(G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,cx,3))
1273                        calc = G2mth.getRestPlane(XYZ,Amat)
1274                        chisq += planeRestData['wtFactor']*((calc)/esd)**2
1275                        table.append([name[:-3],calc,obs,esd])
1276                        rowLabels.append(str(i))
1277                    except KeyError:
1278                        print ('**** WARNING - missing atom - restraint deleted ****')
1279                        bad.append(i)
1280            else:                               
1281                colLabels = ['atom+SymOp','calc','target','esd']
1282                for i,[indx,ops,obs,esd] in enumerate(planeList):
1283                    try:
1284                        atoms = G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,ct-1)
1285                        XYZ = np.array(G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,cx,3))
1286                        XYZ = G2mth.getSyXYZ(XYZ,ops,SGData)
1287                        calc = G2mth.getRestPlane(XYZ,Amat)
1288                        chisq += planeRestData['wtFactor']*((calc)/esd)**2
1289                        name = ''
1290                        for a,atom in enumerate(atoms):
1291                            name += atom+'+ ('+ops[a]+'),'
1292                            if (a+1)%3 == 0:
1293                                name += '\n'
1294                        table.append([name[:-1],calc,obs,esd])
1295                        rowLabels.append(str(i))
1296                    except KeyError:
1297                        print ('**** WARNING - missing atom - restraint deleted ****')
1298                        bad.append(i)
1299            if len(bad):
1300                bad.reverse()
1301                for ibad in bad:
1302                    del planeList[ibad]
1303            if len(planeList):
1304                planeTable = G2G.Table(table,rowLabels=rowLabels,colLabels=colLabels,types=Types)
1305                Planes = G2G.GSGrid(PlaneRestr)
1306                Planes.SetTable(planeTable, True)
1307                Planes.AutoSizeColumns(False)
1308                Planes.AutoSizeRows(False)
1309                for r in range(len(planeList)):
1310                    for c in range(3):
1311                        Planes.SetReadOnly(r,c,True)
1312                        Planes.SetCellStyle(r,c,VERY_LIGHT_GREY,True)
1313                Planes.Bind(wg.EVT_GRID_LABEL_LEFT_CLICK,OnRowSelect)
1314                if 'phoenix' in wx.version():
1315                    Planes.Bind(wg.EVT_GRID_CELL_CHANGED, OnCellChange)
1316                else:
1317                    Planes.Bind(wg.EVT_GRID_CELL_CHANGE, OnCellChange)
1318                for i in (G2G.wxID_RESTDELETE,G2G.wxID_RESTCHANGEESD):
1319                    G2frame.dataWindow.RestraintEdit.Enable(id=i,enable=True)
1320                G2frame.Bind(wx.EVT_MENU, OnDeleteRestraint, id=G2G.wxID_RESTDELETE)
1321                G2frame.Bind(wx.EVT_MENU, OnChangeEsd, id=G2G.wxID_RESTCHANGEESD)
1322                mainSizer.Add(wx.StaticText(PlaneRestr,-1,
1323                    'Plane restraints: sum(wt*(delt/sig)^2) =    %.2f, mean(wt*(delt/sig)^2) =    %.2f'    \
1324                    %(chisq,chisq/len(planeList))),0)
1325                Planes.SetScrollRate(10,10)
1326                Planes.SetMinSize((-1,300))
1327                mainSizer.Add(Planes,1,wx.EXPAND,1)
1328            else:
1329                mainSizer.Add(wx.StaticText(PlaneRestr,-1,'No plane restraints for this phase'),0,)
1330        else:
1331            mainSizer.Add(wx.StaticText(PlaneRestr,-1,'No plane restraints for this phase'),0,)
1332
1333        G2phsGUI.SetPhaseWindow(PlaneRestr,mainSizer,Scroll=0)
1334   
1335    def UpdateChiralRestr(chiralRestData):
1336
1337        def OnCellChange(event):
1338            r,c =  event.GetRow(),event.GetCol()
1339            try:
1340                new = float(volumeTable.GetValue(r,c))
1341                if new <= 0.:
1342                    raise ValueError
1343                chiralRestData['Volumes'][r][c] = new
1344            except ValueError:
1345                pass           
1346            wx.CallAfter(UpdateChiralRestr,chiralRestData)               
1347           
1348        def OnDeleteRestraint(event):
1349            rows = GetSelectedRows(Volumes,'delete',G2frame)
1350            G2frame.GetStatusBar().SetStatusText('',1)
1351            if not rows:
1352                G2frame.GetStatusBar().SetStatusText('First select restraints to be deleted',1)
1353                return
1354            rows.sort()
1355            rows.reverse()
1356            for row in rows:
1357                volumeList.remove(volumeList[row])
1358            UpdateChiralRestr(chiralRestData)               
1359           
1360        def OnChangeValue(event):
1361            rows = GetSelectedRows(Volumes)
1362            if not rows:
1363                return
1364            Volumes.ClearSelection()
1365            val = volumeList[rows[0]][2]
1366            dlg = G2G.SingleFloatDialog(G2frame,'New value','Enter new value for chiral volume',val,[0.,360.],'%.2f')
1367            if dlg.ShowModal() == wx.ID_OK:
1368                parm = dlg.GetValue()
1369                for r in rows:
1370                    chiralRestData['Volumes'][r][2] = parm
1371            dlg.Destroy()
1372            UpdateChiralRestr(chiralRestData)               
1373
1374        def OnChangeEsd(event):
1375            rows = GetSelectedRows(Volumes)
1376            if not rows:
1377                return
1378            Volumes.ClearSelection()
1379            val = volumeList[rows[0]][3]
1380            dlg = G2G.SingleFloatDialog(G2frame,'New value','Enter new esd for chiral volume',val,[0.,5.],'%.2f')
1381            if dlg.ShowModal() == wx.ID_OK:
1382                parm = dlg.GetValue()
1383                for r in rows:
1384                    chiralRestData['Volumes'][r][3] = parm
1385            dlg.Destroy()
1386            UpdateChiralRestr(chiralRestData)               
1387
1388        try:
1389            if ChiralRestr.GetSizer(): ChiralRestr.GetSizer().Clear(True)
1390        except:
1391            pass
1392        mainSizer = wx.BoxSizer(wx.VERTICAL)
1393        mainSizer.Add((5,5),0)
1394        mainSizer.Add(WtBox(ChiralRestr,chiralRestData),0)
1395
1396        for i in (G2G.wxID_RESTDELETE,G2G.wxID_RESRCHANGEVAL,G2G.wxID_RESTCHANGEESD):
1397            G2frame.dataWindow.RestraintEdit.Enable(id=i,enable=False)
1398        volumeList = chiralRestData['Volumes']
1399        if len(volumeList):
1400            table = []
1401            rowLabels = []
1402            bad = []
1403            chisq = 0.
1404            Types = [wg.GRID_VALUE_STRING,]+4*[wg.GRID_VALUE_FLOAT+':10,2',]
1405            if 'macro' in General['Type']:
1406                colLabels = ['(res) O (res) A (res) B (res) C','calc','target','esd','delt/sig']
1407                for i,[indx,ops,obs,esd] in enumerate(volumeList):
1408                    try:
1409                        atoms = G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,0,4)
1410                        name = ''
1411                        for atom in atoms:
1412                            name += '('+atom[1]+atom[0].strip()+atom[2]+') '+atom[3]+' '
1413                        XYZ = np.array(G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,cx,3))
1414                        calc = G2mth.getRestChiral(XYZ,Amat)
1415                        chisq += chiralRestData['wtFactor']*((obs-calc)/esd)**2
1416                        table.append([name,calc,obs,esd,(obs-calc)/esd])
1417                        rowLabels.append(str(i))
1418                    except KeyError:
1419                        print ('**** WARNING - missing atom - restraint deleted ****')
1420                        bad.append(i)
1421            else:
1422                colLabels = ['O+SymOp  A+SymOp  B+SymOp  C+SymOp)','calc','target','esd','delt/sig']
1423                for i,[indx,ops,obs,esd] in enumerate(volumeList):
1424                    try:
1425                        atoms = G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,ct-1)
1426                        name = atoms[0]+'+('+ops[0]+') '+atoms[1]+'+('+ops[1]+') '+atoms[2]+ \
1427                            '+('+ops[2]+') '+atoms[3]+'+('+ops[3]+')'
1428                        XYZ = np.array(G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,cx,3))
1429                        XYZ = G2mth.getSyXYZ(XYZ,ops,SGData)
1430                        calc = G2mth.getRestChiral(XYZ,Amat)
1431                        chisq += chiralRestData['wtFactor']*((obs-calc)/esd)**2
1432                        table.append([name,calc,obs,esd,(obs-calc)/esd])
1433                        rowLabels.append(str(i))
1434                    except KeyError:
1435                        print ('**** WARNING - missing atom - restraint deleted ****')
1436                        bad.append(i)
1437            if len(bad):
1438                bad.reverse()
1439                for ibad in bad:
1440                    del volumeList[ibad]
1441            if len(volumeList):
1442                volumeTable = G2G.Table(table,rowLabels=rowLabels,colLabels=colLabels,types=Types)
1443                Volumes = G2G.GSGrid(ChiralRestr)
1444                Volumes.SetTable(volumeTable, True)
1445                Volumes.AutoSizeColumns(False)
1446                for r in range(len(volumeList)):
1447                    for c in [0,1,4]:
1448                        Volumes.SetReadOnly(r,c,True)
1449                        Volumes.SetCellStyle(r,c,VERY_LIGHT_GREY,True)
1450                Volumes.Bind(wg.EVT_GRID_LABEL_LEFT_CLICK,OnRowSelect)
1451                if 'phoenix' in wx.version():
1452                    Volumes.Bind(wg.EVT_GRID_CELL_CHANGED, OnCellChange)
1453                else:
1454                    Volumes.Bind(wg.EVT_GRID_CELL_CHANGE, OnCellChange)
1455                for i in (G2G.wxID_RESRCHANGEVAL,G2G.wxID_RESTCHANGEESD):
1456                    G2frame.dataWindow.RestraintEdit.Enable(id=i,enable=True)
1457                G2frame.Bind(wx.EVT_MENU, OnChangeValue, id=G2G.wxID_RESRCHANGEVAL)
1458                G2frame.Bind(wx.EVT_MENU, OnChangeEsd, id=G2G.wxID_RESTCHANGEESD)
1459                mainSizer.Add(wx.StaticText(ChiralRestr,-1,
1460                    'Chiral volume restraints: sum(wt*(delt/sig)^2) =    %.2f, mean(wt*(delt/sig)^2) =    %.2f'    \
1461                    %(chisq,chisq/len(volumeList))),0)
1462                Volumes.SetScrollRate(10,10)
1463                Volumes.SetMinSize((-1,300))
1464                mainSizer.Add(Volumes,1,wx.EXPAND,1)
1465            else:
1466                mainSizer.Add(wx.StaticText(ChiralRestr,-1,'No chiral volume restraints for this phase'),0,)
1467        else:
1468            mainSizer.Add(wx.StaticText(ChiralRestr,-1,'No chiral volume restraints for this phase'),0,)
1469
1470        G2phsGUI.SetPhaseWindow(ChiralRestr,mainSizer,Scroll=0)
1471   
1472    def UpdateTorsionRestr(torsionRestData):
1473
1474        def OnCellChange(event):
1475            r,c =  event.GetRow(),event.GetCol()
1476            try:
1477                new = float(torsionTable.GetValue(r,c))
1478                if new <= 0. or new > 5.:
1479                    raise ValueError
1480                torsionRestData['Torsions'][r][3] = new     #only esd is editable
1481            except ValueError:
1482                pass           
1483            wx.CallAfter(UpdateTorsionRestr,torsionRestData)               
1484           
1485        def OnDeleteRestraint(event):
1486            rows = GetSelectedRows(TorsionRestr.Torsions,'delete',G2frame)
1487            G2frame.GetStatusBar().SetStatusText('',1)
1488            if not rows:
1489                G2frame.GetStatusBar().SetStatusText('First select restraints to be deleted',1)
1490                return
1491            rows.sort()
1492            rows.reverse()
1493            for row in rows:
1494                torsionList.remove(torsionList[row])
1495            wx.CallAfter(UpdateTorsionRestr,torsionRestData)               
1496           
1497        def OnChangeEsd(event):
1498            rows = GetSelectedRows(TorsionRestr.Torsions)
1499            if not rows:
1500                return
1501            TorsionRestr.Torsions.ClearSelection()
1502            val = torsionList[rows[0]][4]
1503            dlg = G2G.SingleFloatDialog(G2frame,'New value','Enter new esd for torsion restraints',val,[0.,5.],'%.2f')
1504            if dlg.ShowModal() == wx.ID_OK:
1505                parm = dlg.GetValue()
1506                for r in rows:
1507                    torsionRestData['Torsions'][r][4] = parm
1508            dlg.Destroy()
1509            wx.CallAfter(UpdateTorsionRestr,torsionRestData) 
1510
1511        def coeffSizer():               
1512            table = []
1513            rowLabels = []
1514            Types = 9*[wg.GRID_VALUE_FLOAT+':10,4',]
1515            colLabels = ['Mag A','Pos A','Width A','Mag B','Pos B','Width B','Mag C','Pos C','Width C']
1516            for item in coeffDict:
1517                rowLabels.append(item)
1518                table.append(coeffDict[item])
1519            coeffTable = G2G.Table(table,rowLabels=rowLabels,colLabels=colLabels,types=Types)
1520            Coeff = G2G.GSGrid(TorsionRestr)
1521            Coeff.SetScrollRate(0,10)
1522            Coeff.SetTable(coeffTable, True)
1523            Coeff.AutoSizeColumns(False)
1524            for r in range(len(coeffDict)):
1525                for c in range(9):
1526                    Coeff.SetReadOnly(r,c,True)
1527                    Coeff.SetCellStyle(r,c,VERY_LIGHT_GREY,True)
1528            Coeff.SetMaxSize((-1,200))
1529            return Coeff
1530                                           
1531        try:
1532            if TorsionRestr.GetSizer(): TorsionRestr.GetSizer().Clear(True)
1533        except:
1534            pass
1535        mainSizer = wx.BoxSizer(wx.VERTICAL)
1536        mainSizer.Add((5,5),0)
1537        mainSizer.Add(WtBox(TorsionRestr,torsionRestData),0)
1538       
1539        coeffDict = torsionRestData['Coeff']
1540        torsionList = torsionRestData['Torsions']
1541        if len(coeffDict):
1542            mainSizer.Add((5,5))
1543            mainSizer.Add(wx.StaticText(TorsionRestr,-1,'Torsion function coefficients:'),0)
1544            mainSizer.Add(coeffSizer(),1,wx.EXPAND,1)       
1545       
1546        for i in (G2G.wxID_RESTDELETE,G2G.wxID_RESRCHANGEVAL,G2G.wxID_RESTCHANGEESD):
1547            G2frame.dataWindow.RestraintEdit.Enable(id=i,enable=False)
1548        if len(torsionList):
1549            mainSizer.Add(wx.StaticText(TorsionRestr,-1,'Torsion restraints:'),0)
1550            table = []
1551            rowLabels = []
1552            bad = []
1553            chisq = 0.
1554            Types = 2*[wg.GRID_VALUE_STRING,]+4*[wg.GRID_VALUE_FLOAT+':10,2',]
1555            if 'macro' in General['Type']:
1556                colLabels = ['(res) A  B  C  D','coef name','torsion','obs E','restr','esd']
1557                for i,[indx,ops,cofName,esd] in enumerate(torsionList):
1558                    try:
1559                        atoms = G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,0,4)
1560                        name = '('+atoms[2][1]+atoms[2][0].strip()+atoms[2][2]+')'
1561                        for atom in atoms:
1562                            name += '  '+atom[3]
1563                        XYZ = np.array(G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,cx,3))
1564                        tor = G2mth.getRestTorsion(XYZ,Amat)
1565                        restr,calc = G2mth.calcTorsionEnergy(tor,coeffDict[cofName])
1566                        chisq += torsionRestData['wtFactor']*(restr/esd)**2
1567                        table.append([name,cofName,tor,calc,restr,esd])
1568                        rowLabels.append(str(i))
1569                    except KeyError:
1570                        print ('**** WARNING - missing atom - restraint deleted ****')
1571                        bad.append(i)
1572            if len(bad):
1573                bad.reverse()
1574                for ibad in bad:
1575                    del torsionList[ibad]
1576            torsionTable = G2G.Table(table,rowLabels=rowLabels,colLabels=colLabels,types=Types)
1577            TorsionRestr.Torsions = G2G.GSGrid(TorsionRestr)
1578            TorsionRestr.Torsions.SetTable(torsionTable, True)
1579            TorsionRestr.Torsions.AutoSizeColumns(False)
1580            for r in range(len(torsionList)):
1581                for c in range(5):
1582                    TorsionRestr.Torsions.SetReadOnly(r,c,True)
1583                    TorsionRestr.Torsions.SetCellStyle(r,c,VERY_LIGHT_GREY,True)
1584            TorsionRestr.Torsions.Bind(wg.EVT_GRID_LABEL_LEFT_CLICK,OnRowSelect)
1585            if 'phoenix' in wx.version():
1586                TorsionRestr.Torsions.Bind(wg.EVT_GRID_CELL_CHANGED, OnCellChange)
1587            else:
1588                TorsionRestr.Torsions.Bind(wg.EVT_GRID_CELL_CHANGE, OnCellChange)
1589            for i in (G2G.wxID_RESTDELETE,G2G.wxID_RESTCHANGEESD):
1590                G2frame.dataWindow.RestraintEdit.Enable(id=i,enable=True)
1591            G2frame.Bind(wx.EVT_MENU, OnDeleteRestraint, id=G2G.wxID_RESTDELETE)
1592            G2frame.Bind(wx.EVT_MENU, OnChangeEsd, id=G2G.wxID_RESTCHANGEESD)
1593            mainSizer.Add(wx.StaticText(TorsionRestr,-1,
1594                'Torsion restraints: sum(wt*(delt/sig)^2) =    %.2f, mean(wt*(delt/sig)^2) =    %.2f'    \
1595                %(chisq,chisq/len(torsionList))),0)
1596            TorsionRestr.Torsions.SetScrollRate(10,10)
1597            TorsionRestr.Torsions.SetMinSize((-1,300))
1598            mainSizer.Add(TorsionRestr.Torsions,1,wx.EXPAND,1)
1599               
1600        else:
1601            mainSizer.Add(wx.StaticText(TorsionRestr,-1,'No torsion restraints for this phase'),0,)
1602
1603        G2phsGUI.SetPhaseWindow(TorsionRestr,mainSizer,Scroll=0)
1604
1605    def UpdateRamaRestr(ramaRestData):
1606
1607        def OnCellChange(event):
1608            r,c =  event.GetRow(),event.GetCol()
1609            try:
1610                new = float(ramaTable.GetValue(r,c))
1611                if new <= 0. or new > 5.:
1612                    raise ValueError
1613                ramaRestData['Ramas'][r][4] = new     #only esd is editable
1614            except ValueError:
1615                pass           
1616            wx.CallAfter(UpdateRamaRestr,ramaRestData)               
1617           
1618        def OnDeleteRestraint(event):
1619            rows = GetSelectedRows(RamaRestr.Ramas,'delete',G2frame)
1620            G2frame.GetStatusBar().SetStatusText('',1)
1621            if not rows:
1622                G2frame.GetStatusBar().SetStatusText('First select restraints to be deleted',1)
1623                return
1624            rows.sort()
1625            rows.reverse()
1626            for row in rows:
1627                ramaList.remove(ramaList[row])
1628            UpdateRamaRestr(ramaRestData)               
1629           
1630        def OnChangeEsd(event):
1631            rows = GetSelectedRows(RamaRestr.Ramas)
1632            if not rows:
1633                return
1634            RamaRestr.Ramas.ClearSelection()
1635            val = ramaList[rows[0]][4]
1636            dlg = G2G.SingleFloatDialog(G2frame,'New value','Enter new esd for energy',val,[0.,5.],'%.2f')
1637            if dlg.ShowModal() == wx.ID_OK:
1638                parm = dlg.GetValue()
1639                for r in rows:
1640                    ramaRestData['Ramas'][r][4] = parm
1641            dlg.Destroy()
1642            UpdateRamaRestr(ramaRestData)
1643
1644        def coeffSizer():
1645            table = []
1646            rowLabels = []
1647            Types = 6*[wg.GRID_VALUE_FLOAT+':10,4',]
1648            colLabels = ['Mag','Pos phi','Pos psi','sig(phi)','sig(psi)','sig(cov)']
1649            for item in coeffDict:
1650                for i,term in enumerate(coeffDict[item]):
1651                    rowLabels.append(item+' term:'+str(i))
1652                    table.append(term)
1653            coeffTable = G2G.Table(table,rowLabels=rowLabels,colLabels=colLabels,types=Types)
1654            Coeff = G2G.GSGrid(RamaRestr)
1655            Coeff.SetScrollRate(0,10)
1656            Coeff.SetTable(coeffTable, True)
1657            Coeff.AutoSizeColumns(False)
1658            for r in range(Coeff.GetNumberRows()):
1659                for c in range(6):
1660                    Coeff.SetReadOnly(r,c,True)
1661                    Coeff.SetCellStyle(r,c,VERY_LIGHT_GREY,True)
1662            Coeff.SetMaxSize((-1,200))
1663            return Coeff
1664                                                   
1665        try:
1666            if RamaRestr.GetSizer(): RamaRestr.GetSizer().Clear(True)
1667        except:
1668            pass
1669        mainSizer = wx.BoxSizer(wx.VERTICAL)
1670        mainSizer.Add((5,5),0)
1671        mainSizer.Add(WtBox(RamaRestr,ramaRestData),0)
1672        ramaList = ramaRestData['Ramas']
1673        coeffDict = ramaRestData['Coeff']
1674        if len(coeffDict):
1675            mainSizer.Add(wx.StaticText(RamaRestr,-1,'Ramachandran function coefficients:'),0)
1676            mainSizer.Add(coeffSizer(),1,wx.EXPAND,1)
1677           
1678        if len(ramaList):
1679            mainSizer.Add(wx.StaticText(RamaRestr,-1,'Ramachandran restraints:'),0)
1680            table = []
1681            rowLabels = []
1682            bad = []
1683            chisq = 0.
1684            Types = 2*[wg.GRID_VALUE_STRING,]+5*[wg.GRID_VALUE_FLOAT+':10,2',]
1685            if 'macro' in General['Type']:
1686                colLabels = ['(res) A  B  C  D  E','coef name','phi','psi','obs E','restr','esd']
1687                for i,[indx,ops,cofName,esd] in enumerate(ramaList):
1688                    try:
1689                        atoms = G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,0,4)
1690                        name = '('+atoms[3][1]+atoms[3][0].strip()+atoms[3][2]+')'
1691                        for atom in atoms:
1692                            name += '  '+atom[3]
1693                        XYZ = np.array(G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,cx,3))
1694                        phi,psi = G2mth.getRestRama(XYZ,Amat)
1695                        restr,calc = G2mth.calcRamaEnergy(phi,psi,coeffDict[cofName])
1696                        chisq += ramaRestData['wtFactor']*(restr/esd)**2
1697                        table.append([name,cofName,phi,psi,calc,restr,esd])
1698                        rowLabels.append(str(i))
1699                    except KeyError:
1700                        print ('**** WARNING - missing atom - restraint deleted ****')
1701                        bad.append(i)
1702            if len(bad):
1703                bad.reverse()
1704                for ibad in bad:
1705                    del ramaList[ibad]
1706            for i in (G2G.wxID_RESTDELETE,G2G.wxID_RESRCHANGEVAL,G2G.wxID_RESTCHANGEESD):
1707                G2frame.dataWindow.RestraintEdit.Enable(id=i,enable=False)
1708            if len(ramaList):
1709                ramaTable = G2G.Table(table,rowLabels=rowLabels,colLabels=colLabels,types=Types)
1710                RamaRestr.Ramas = G2G.GSGrid(RamaRestr)
1711                RamaRestr.Ramas.SetTable(ramaTable, True)
1712                RamaRestr.Ramas.AutoSizeColumns(False)
1713                for r in range(len(ramaList)):
1714                    for c in range(6):
1715                        RamaRestr.Ramas.SetReadOnly(r,c,True)
1716                        RamaRestr.Ramas.SetCellStyle(r,c,VERY_LIGHT_GREY,True)
1717                RamaRestr.Ramas.Bind(wg.EVT_GRID_LABEL_LEFT_CLICK,OnRowSelect)
1718                if 'phoenix' in wx.version():
1719                    RamaRestr.Ramas.Bind(wg.EVT_GRID_CELL_CHANGED, OnCellChange)
1720                else:
1721                    RamaRestr.Ramas.Bind(wg.EVT_GRID_CELL_CHANGE, OnCellChange)
1722                for i in (G2G.wxID_RESTDELETE,G2G.wxID_RESTCHANGEESD):
1723                    G2frame.dataWindow.RestraintEdit.Enable(id=i,enable=True)
1724                G2frame.Bind(wx.EVT_MENU, OnDeleteRestraint, id=G2G.wxID_RESTDELETE)
1725                G2frame.Bind(wx.EVT_MENU, OnChangeEsd, id=G2G.wxID_RESTCHANGEESD)
1726                mainSizer.Add(wx.StaticText(RamaRestr,-1,
1727                    'Ramachandran restraints: sum(wt*(delt/sig)^2) =    %.2f, mean(wt*(delt/sig)^2) =    %.2f'    \
1728                    %(chisq,chisq/len(ramaList))),0)
1729                RamaRestr.Ramas.SetScrollRate(10,10)
1730                RamaRestr.Ramas.SetMinSize((-1,300))
1731                mainSizer.Add(RamaRestr.Ramas,1,wx.EXPAND,1)
1732        else:
1733            mainSizer.Add(wx.StaticText(RamaRestr,-1,'No Ramachandran restraints for this phase'),0,)
1734
1735        G2phsGUI.SetPhaseWindow(RamaRestr,mainSizer,Scroll=0)
1736
1737    def UpdateChemcompRestr(chemcompRestData):
1738       
1739        def OnCellChange(event):
1740            r,c =  event.GetRow(),event.GetCol()
1741            rowLabl = ChemComps.GetRowLabelValue(r)
1742            row = int(rowLabl.split(':')[1])
1743            if 'Restr' in rowLabl:
1744                try:
1745                    new = float(chemcompTable.GetValue(r,c))
1746                    chemcompRestData['Sites'][row][c-2] = new         #obsd or esd
1747                except ValueError:
1748                    pass
1749            else:
1750                try:
1751                    new = float(chemcompTable.GetValue(r,c))
1752                    id = int(rowLabl.split(':')[2])
1753                    chemcompRestData['Sites'][row][1][id] = new     #only factor
1754                except ValueError:
1755                    pass
1756            wx.CallAfter(UpdateChemcompRestr,chemcompRestData)               
1757           
1758        def OnDeleteRestraint(event):
1759            rows = GetSelectedRows(ChemComps,'delete',G2frame)
1760            #rows = ChemComps.GetSelectedRows()
1761            G2frame.GetStatusBar().SetStatusText('',1)
1762            if not rows:
1763                G2frame.GetStatusBar().SetStatusText('First select restraints to be deleted',1)
1764                return
1765            terms = []
1766            restrs = []
1767            for r in sorted(rows,reverse=True):
1768                rowLabl = ChemComps.GetRowLabelValue(r)               
1769                if 'Restr' in rowLabl:
1770                    restrs.append(int(rowLabl.split(':')[1]))
1771                else:
1772                    terms.append([int(i) for i in rowLabl.split(':')[1:]])
1773            # delete terms first in case someone deletes a term in a restraint to be deleted
1774            for row,term in terms:           
1775                del chemcompList[row][0][term]
1776                del chemcompList[row][1][term]
1777            for row in restrs:
1778                del chemcompList[row]
1779            UpdateChemcompRestr(chemcompRestData)               
1780           
1781        # def OnChangeValue(event):
1782        #     rows = GetSelectedRows(ChemComps)
1783        #     if not rows:
1784        #         return
1785        #     ChemComps.ClearSelection()
1786        #     dlg = G2G.SingleFloatDialog(G2frame,'New value',
1787        #         'Enter new value for restraint multiplier',1.0,[-1.e6,1.e6],'%.2f')
1788        #     if dlg.ShowModal() == wx.ID_OK:
1789        #         parm = dlg.GetValue()
1790        #         for r in rows:
1791        #             rowLabl = ChemComps.GetRowLabelValue(r)
1792        #             if 'term' in rowLabl:
1793        #                 items = rowLabl.split(':')
1794        #                 chemcompRestData['Sites'][int(items[1])][1][int(items[2])] = parm
1795        #     dlg.Destroy()
1796        #     UpdateChemcompRestr(chemcompRestData)               
1797
1798        try:
1799            if ChemCompRestr.GetSizer(): ChemCompRestr.GetSizer().Clear(True)
1800        except:
1801            pass
1802        mainSizer = wx.BoxSizer(wx.VERTICAL)
1803        mainSizer.Add((5,5),0)
1804        mainSizer.Add(WtBox(ChemCompRestr,chemcompRestData),0)
1805        mainSizer.Add(wx.StaticText(ChemCompRestr,-1, 
1806            'NB: The chemical restraint sum is over the unit cell contents'),0)
1807        mainSizer.Add((5,5),0)
1808
1809        for i in (G2G.wxID_RESTDELETE,G2G.wxID_RESRCHANGEVAL,G2G.wxID_RESTCHANGEESD):
1810            G2frame.dataWindow.RestraintEdit.Enable(id=i,enable=False)
1811        chemcompList = chemcompRestData['Sites']
1812        if len(chemcompList):
1813            table = []
1814            rowLabels = []
1815            bad = []
1816            chisq = 0.
1817            Types = [wg.GRID_VALUE_STRING,]+5*[wg.GRID_VALUE_FLOAT+':10,2',]
1818            colLabels = ['Atoms','mul*frac','factor','calc','target','esd']
1819            for i,[indx,factors,obs,esd] in enumerate(chemcompList):
1820                try:
1821                    atoms = G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,ct-1)
1822                    mul = np.array(G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,cs+1))
1823                    frac = np.array(G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,cs-1))
1824                    mulfrac = mul*frac
1825                    calcs = mul*frac*factors
1826                    chisq += chemcompRestData['wtFactor']*((obs-np.sum(calcs))/esd)**2
1827                    for iatm,[atom,mf,fr,clc] in enumerate(zip(atoms,mulfrac,factors,calcs)):
1828                        table.append([atom,mf,fr,clc,'',''])
1829                        rowLabels.append('term:'+str(i)+':'+str(iatm))
1830                    table.append(['(Sum)','','',np.sum(calcs),obs,esd])
1831                    rowLabels.append('Restr:'+str(i))
1832                except KeyError:
1833                    print ('**** WARNING - missing atom - restraint deleted ****')
1834                    bad.append(i)
1835            if len(bad):
1836                bad.reverse()
1837                for ibad in bad:
1838                    del chemcompList[ibad]
1839            if len(chemcompList):
1840                chemcompTable = G2G.Table(table,rowLabels=rowLabels,colLabels=colLabels,types=Types)
1841                ChemComps = G2G.GSGrid(ChemCompRestr)
1842                ChemComps.SetTable(chemcompTable, True)
1843                ChemComps.AutoSizeColumns(False)
1844                for r in range(chemcompTable.GetNumberRows()):
1845                    for c in range(2):
1846                        ChemComps.SetReadOnly(r,c,True)
1847                        ChemComps.SetCellStyle(r,c,VERY_LIGHT_GREY,True)
1848                    if 'Restr' in ChemComps.GetRowLabelValue(r):
1849                        for c in range(4):
1850                            ChemComps.SetReadOnly(r,c,True)
1851                            ChemComps.SetCellStyle(r,c,VERY_YELLOW,True)
1852                        ChemComps.SetCellTextColour(r,1,VERY_YELLOW) # make spurious #'s disappear
1853                        ChemComps.SetCellTextColour(r,2,VERY_YELLOW)
1854                    else:
1855                        for c in [3,4,5]:
1856                            ChemComps.SetReadOnly(r,c,True)
1857                            ChemComps.SetCellStyle(r,c,VERY_LIGHT_GREY,True)
1858                        for c in [4,5]:
1859                            ChemComps.SetCellTextColour(r,c,VERY_LIGHT_GREY)
1860                           
1861                ChemComps.Bind(wg.EVT_GRID_LABEL_LEFT_CLICK,OnRowSelect)
1862                if 'phoenix' in wx.version():
1863                    ChemComps.Bind(wg.EVT_GRID_CELL_CHANGED, OnCellChange)
1864                else:
1865                    ChemComps.Bind(wg.EVT_GRID_CELL_CHANGE, OnCellChange)
1866                G2frame.dataWindow.RestraintEdit.Enable(id=G2G.wxID_RESTDELETE,enable=True)
1867                G2frame.Bind(wx.EVT_MENU, OnDeleteRestraint, id=G2G.wxID_RESTDELETE)
1868                #G2frame.Bind(wx.EVT_MENU, OnChangeValue, id=G2G.wxID_RESRCHANGEVAL)
1869                mainSizer.Add(wx.StaticText(ChemCompRestr,-1,
1870                    'Chemical composition restraints: sum(wt*(delt/sig)^2) =    %.2f, mean(wt*(delt/sig)^2) =    %.2f'    \
1871                    %(chisq,chisq/len(chemcompList))))
1872                mainSizer.Add(ChemComps)
1873            else:
1874                mainSizer.Add(wx.StaticText(ChemCompRestr,-1,'No chemical composition restraints for this phase'),0,)
1875        else:
1876            mainSizer.Add(wx.StaticText(ChemCompRestr,-1,'No chemical composition restraints for this phase'),0,)
1877
1878        G2phsGUI.SetPhaseWindow(ChemCompRestr,mainSizer,Scroll=0)
1879           
1880    def UpdateTextureRestr(textureRestData):
1881           
1882        def OnDeleteRestraint(event):
1883            rows = GetSelectedRows(Textures,'delete',G2frame)
1884            G2frame.GetStatusBar().SetStatusText('',1)
1885            if not rows:
1886                G2frame.GetStatusBar().SetStatusText('First select restraints to be deleted',1)
1887                return
1888            rows.sort()
1889            rows.reverse()
1890            for row in rows:
1891                textureList.remove(textureList[row])
1892            wx.CallAfter(UpdateTextureRestr,textureRestData)               
1893           
1894        def OnCellChange(event):
1895            r,c = event.GetRow(),event.GetCol()
1896            try:
1897                if c == 1:  #grid size
1898                    new = int(textureTable.GetValue(r,c))
1899                    if new < 6 or new > 24:
1900                        raise ValueError
1901                elif c in [2,4]:   #esds
1902                    new = float(textureTable.GetValue(r,c))
1903                    if new < -1. or new > 2.:
1904                        raise ValueError
1905                else:
1906                    new = textureTable.GetValue(r,c)
1907                textureRestData['HKLs'][r][c] = new
1908            except ValueError:
1909                pass           
1910            wx.CallAfter(UpdateTextureRestr,textureRestData)               
1911
1912        try:
1913            if TextureRestr.GetSizer(): TextureRestr.GetSizer().Clear(True)
1914        except:
1915            pass
1916        mainSizer = wx.BoxSizer(wx.VERTICAL)
1917        mainSizer.Add((5,5),0)
1918        mainSizer.Add(WtBox(TextureRestr,textureRestData),0)
1919        mainSizer.Add(wx.StaticText(TextureRestr,-1, 
1920            'NB: The texture restraints suppress negative pole figure values for the selected HKLs\n'
1921            '    "unit esd" gives a bias toward a flatter polefigure'),0)
1922        mainSizer.Add((5,5),0)
1923
1924        for i in (G2G.wxID_RESTDELETE,G2G.wxID_RESRCHANGEVAL,G2G.wxID_RESTCHANGEESD):
1925            G2frame.dataWindow.RestraintEdit.Enable(id=i,enable=False)
1926        textureList = textureRestData['HKLs']
1927        if len(textureList):
1928            table = []
1929            rowLabels = []
1930            Types = [wg.GRID_VALUE_STRING,wg.GRID_VALUE_LONG,wg.GRID_VALUE_FLOAT+':10,2',
1931                wg.GRID_VALUE_BOOL,wg.GRID_VALUE_FLOAT+':10,2']
1932            colLabels = ['HKL','grid','neg esd','use unit?','unit esd']
1933            for i,[hkl,grid,esd1,ifesd2,esd2] in enumerate(textureList):
1934                table.append(['%d %d %d'%(hkl[0],hkl[1],hkl[2]),grid,esd1,ifesd2,esd2])
1935                rowLabels.append(str(i))
1936            textureTable = G2G.Table(table,rowLabels=rowLabels,colLabels=colLabels,types=Types)
1937            Textures = G2G.GSGrid(TextureRestr)
1938            Textures.SetTable(textureTable, True)
1939            Textures.AutoSizeColumns(False)
1940            for r in range(len(textureList)):
1941                Textures.SetReadOnly(r,0,True)
1942                Textures.SetCellStyle(r,0,VERY_LIGHT_GREY,True)
1943                if not textureTable.GetValue(r,3):
1944                    Textures.SetReadOnly(r,4,True)
1945                    Textures.SetCellStyle(r,4,VERY_LIGHT_GREY,True)
1946                    Textures.SetCellTextColour(r,4,VERY_LIGHT_GREY)
1947            Textures.Bind(wg.EVT_GRID_LABEL_LEFT_CLICK,OnRowSelect)
1948            if 'phoenix' in wx.version():
1949                Textures.Bind(wg.EVT_GRID_CELL_CHANGED, OnCellChange)
1950            else:
1951                Textures.Bind(wg.EVT_GRID_CELL_CHANGE, OnCellChange)
1952            G2frame.dataWindow.RestraintEdit.Enable(id=G2G.wxID_RESTDELETE,enable=True)
1953            G2frame.Bind(wx.EVT_MENU, OnDeleteRestraint, id=G2G.wxID_RESTDELETE)
1954            mainSizer.Add(Textures,0,)
1955        else:
1956            mainSizer.Add(wx.StaticText(TextureRestr,-1,'No texture restraints for this phase'),0,)
1957        G2phsGUI.SetPhaseWindow(TextureRestr,mainSizer,Scroll=0)
1958           
1959    def UpdateGeneralRestr(generalRestData):
1960        '''Display any generalized restraint expressions'''
1961       
1962        def OnEditGenRestraint(event):
1963            '''Edit a restraint expression'''
1964            n = event.GetEventObject().index
1965            parmDict = SetupParmDict(G2frame)
1966            dlg = G2exG.ExpressionDialog(G2frame,parmDict,
1967                exprObj=generalRestData['General'][n][0],
1968                header="Edit a restraint expression",
1969                fit=False,wildCard=G2frame.testSeqRefineMode())
1970            restobj = dlg.Show(True)
1971            if restobj:
1972                generalRestData['General'][n][0] = restobj
1973                wx.CallAfter(UpdateGeneralRestr,restrData['General'])
1974               
1975        def OnDelGenRestraint(event):              #does this work??
1976            '''Delete a restraint expression'''
1977            n = event.GetEventObject().index
1978            del restrData['General']['General'][n]
1979            wx.CallAfter(UpdateGeneralRestr,restrData['General'])
1980
1981        try:
1982            if GeneralRestr.GetSizer(): GeneralRestr.GetSizer().Clear(True)
1983        except:
1984            pass
1985        mainSizer = wx.BoxSizer(wx.VERTICAL)
1986        mainSizer.Add((5,5),0)
1987        hSizer = wx.BoxSizer(wx.HORIZONTAL)
1988        hSizer.Add(wx.StaticText(GeneralRestr,wx.ID_ANY,'Weight factor: '))
1989        hSizer.Add(G2G.ValidatedTxtCtrl(GeneralRestr,generalRestData,
1990            'wtFactor',nDig=(10,1),typeHint=float))
1991        btn = G2G.G2CheckBox(GeneralRestr,'Use?',generalRestData,'Use')
1992        hSizer.Add(btn)
1993        hSizer.Add((5,5),0)
1994        btn = wx.Button(GeneralRestr, wx.ID_ANY,"Add restraint")
1995        btn.Bind(wx.EVT_BUTTON,OnAddRestraint)
1996        hSizer.Add(btn,0,wx.EXPAND|wx.ALL)
1997        mainSizer.Add(hSizer,0)
1998        mainSizer.Add((5,5),0)
1999        for i in (G2G.wxID_RESTDELETE,G2G.wxID_RESRCHANGEVAL,G2G.wxID_RESTCHANGEESD):
2000            G2frame.dataWindow.RestraintEdit.Enable(id=i,enable=False)
2001        if generalRestData['General']:
2002            parmDict = SetupParmDict(G2frame)
2003            GridSiz = wx.FlexGridSizer(0,9,10,2)
2004            GridSiz.Add((-1,-1))
2005            GridSiz.Add(
2006                    wx.StaticText(GeneralRestr,wx.ID_ANY,'Expression'),
2007                    0,wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL)
2008            GridSiz.Add((-1,-1))
2009#            for lbl in ('expression',' ','target\nvalue','current\nvalue','esd'):
2010            for lbl in ('target\nvalue','current\nvalue','esd'):
2011                GridSiz.Add(
2012                    wx.StaticText(GeneralRestr,wx.ID_ANY,lbl,style=wx.CENTER),
2013                    0,wx.ALIGN_CENTER)
2014            GridSiz.Add((-1,-1))
2015            GridSiz.Add((-1,-1))
2016            GridSiz.Add(
2017                    wx.StaticText(GeneralRestr,wx.ID_ANY,'Variables',style=wx.CENTER),
2018                    0,wx.ALIGN_CENTER)
2019            for i,rest in enumerate(generalRestData['General']):
2020                eq = rest[0]
2021                txt = '{}: '.format(i+1)
2022                GridSiz.Add(wx.StaticText(GeneralRestr,wx.ID_ANY,txt))
2023                txt = eq.expression
2024                if len(txt) > 50:
2025                    txt = txt[:47]+'... '
2026                txtC = wx.StaticText(GeneralRestr,wx.ID_ANY,txt)
2027                GridSiz.Add(txtC)
2028                GridSiz.Add(wx.StaticText(GeneralRestr,wx.ID_ANY,' = '))
2029                GridSiz.Add(
2030                    G2G.ValidatedTxtCtrl(GeneralRestr,rest,1,nDig=(10,3,'g'),typeHint=float)
2031                    )
2032                # evaluate the expression
2033                try:
2034                    calcobj = G2obj.ExpressionCalcObj(rest[0])
2035                    calcobj.SetupCalc(parmDict)
2036                    txt = ' {:f} '.format(calcobj.EvalExpression())
2037                except:
2038                    txt = ' (error) '
2039                    txtC.SetForegroundColour("red")
2040                GridSiz.Add(wx.StaticText(GeneralRestr,wx.ID_ANY,txt))
2041                GridSiz.Add(
2042                    G2G.ValidatedTxtCtrl(GeneralRestr,rest,2,nDig=(10,3,'g'),typeHint=float)
2043                    )
2044                btn = wx.Button(GeneralRestr, wx.ID_ANY,"Edit",size=(40,-1))
2045                btn.index = i
2046                btn.Bind(wx.EVT_BUTTON,OnEditGenRestraint)
2047                GridSiz.Add(btn)
2048                btn = wx.Button(GeneralRestr, wx.ID_ANY,"Delete",size=(60,-1))
2049                btn.index = i
2050                btn.Bind(wx.EVT_BUTTON,OnDelGenRestraint)
2051                GridSiz.Add(btn)
2052                txt = ''
2053                for i in eq.assgnVars:
2054                    if txt: txt += '; '
2055                    txt += str(i) + '=' + str(eq.assgnVars[i])
2056                if len(txt) > 50:
2057                    txt = txt[:47]+'...'
2058                GridSiz.Add(wx.StaticText(GeneralRestr,wx.ID_ANY,txt))
2059            mainSizer.Add(GridSiz)
2060        G2phsGUI.SetPhaseWindow(GeneralRestr,mainSizer,Scroll=0)
2061
2062   
2063    def OnPageChanged(event):
2064        page = event.GetSelection()
2065        #G2frame.restrBook.SetSize(G2frame.dataWindow.GetClientSize())    #TODO -almost right
2066        text = G2frame.restrBook.GetPageText(page)
2067        G2frame.dataWindow.RestraintEdit.SetLabel(G2G.wxID_RESRCHANGEVAL,'Change value')
2068        G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_USEMOGUL,False)
2069        if text == 'Bond':
2070            G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.RestraintMenu)
2071            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_RESTRAINTADD,True)
2072            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_RESRCHANGEVAL,True)
2073            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_USEMOGUL,True)
2074            bondRestData = restrData['Bond']
2075            UpdateBondRestr(bondRestData)
2076        elif text == 'Angle':
2077            G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.RestraintMenu)
2078            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_RESTRAINTADD,True)
2079            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_RESRCHANGEVAL,True)
2080            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_USEMOGUL,True)
2081            angleRestData = restrData['Angle']
2082            UpdateAngleRestr(angleRestData)
2083        elif text == 'Plane':
2084            G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.RestraintMenu)
2085            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_RESTRAINTADD,True)
2086            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_RESRCHANGEVAL,False)
2087            planeRestData = restrData['Plane']
2088            UpdatePlaneRestr(planeRestData)
2089        elif text == 'Chiral':
2090            G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.RestraintMenu)
2091            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_RESTRAINTADD,False)
2092            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_RESRCHANGEVAL,True)
2093            chiralRestData = restrData['Chiral']
2094            UpdateChiralRestr(chiralRestData)
2095        elif text == 'Torsion':
2096            G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.RestraintMenu)
2097            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_RESTRAINTADD,False)
2098            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_RESRCHANGEVAL,False)
2099            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_AARESTRAINTPLOT,True)
2100            torsionRestData = restrData['Torsion']
2101            UpdateTorsionRestr(torsionRestData)
2102        elif text == 'Ramachandran':
2103            G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.RestraintMenu)
2104            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_RESTRAINTADD,False)
2105            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_RESRCHANGEVAL,False)
2106            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_AARESTRAINTPLOT,True)
2107            ramaRestData = restrData['Rama']
2108            UpdateRamaRestr(ramaRestData)
2109            wx.CallAfter(G2plt.PlotRama,G2frame,phaseName,rama,ramaName)
2110        elif text == 'Chem. comp.':
2111            G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.RestraintMenu)
2112            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_RESTRAINTADD,True)
2113            G2frame.dataWindow.RestraintEdit.SetLabel(G2G.wxID_RESRCHANGEVAL,'Change factor')
2114            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_RESRCHANGEVAL,True)
2115            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_RESTCHANGEESD,False)
2116            chemcompRestData = restrData['ChemComp']
2117            UpdateChemcompRestr(chemcompRestData)
2118        elif text == 'Texture':
2119            G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.RestraintMenu)
2120            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_RESTRAINTADD,True)
2121            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_RESRCHANGEVAL,True)
2122            textureRestData = restrData['Texture']
2123            UpdateTextureRestr(textureRestData)
2124        elif text == 'General':
2125            G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.RestraintMenu)
2126            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_RESTRAINTADD,True)
2127            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_RESRCHANGEVAL,False)
2128            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_RESTCHANGEESD,False)
2129            UpdateGeneralRestr(restrData['General'])
2130        event.Skip()
2131
2132#    def RaisePage(event):
2133#        'Respond to a "select tab" menu button'
2134#        # class PseudoEvent(object):
2135#        #     def __init__(self,page): self.page = page
2136#        #     def Skip(self): pass
2137#        #     def GetSelection(self): return self.page
2138#        try:
2139#            i = tabIndex.get(event.GetId())
2140#            G2frame.restrBook.SetSelection(i)
2141#            #OnPageChanged(PseudoEvent(i))
2142#        except ValueError:
2143#            print('Unexpected event in RaisePage')
2144#
2145    def OnSelectPage(event):
2146        'Called when an item is selected from the Select page menu'
2147        # lookup the menu item that called us and get its text
2148        tabname = TabSelectionIdDict.get(event.GetId())
2149        if not tabname:
2150            print ('Warning: menu item not in dict! id= %d'%event.GetId())
2151            return
2152        # find the matching tab
2153        for PageNum in range(G2frame.restrBook.GetPageCount()):
2154            if tabname == G2frame.restrBook.GetPageText(PageNum):
2155                G2frame.restrBook.SetSelection(PageNum)
2156                return
2157        else:
2158            print ("Warning: tab "+tabname+" was not found")
2159
2160    # UpdateRestraints execution starts here
2161    try:
2162        phasedata = G2frame.GetPhaseData()[phaseName]
2163    except KeyError:        #delete unknown or previously deleted phases from Restraints
2164        rId = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'Restraints')
2165        pId = G2gd.GetGPXtreeItemId(G2frame,rId,phaseName)
2166        G2frame.GPXtree.Delete(pId)
2167        print('Unknown phase '+phaseName+' is deleted from Restraints')
2168        return
2169    restrData = data[phaseName]
2170    if 'Bond' not in restrData:
2171        restrData['Bond'] = {'wtFactor':1.0,'Range':1.1,'Bonds':[],'Use':True}
2172    if 'Angle' not in restrData:
2173        restrData['Angle'] = {'wtFactor':1.0,'Range':0.85,'Angles':[],'Use':True}
2174    if 'Plane' not in restrData:
2175        restrData['Plane'] = {'wtFactor':1.0,'Planes':[],'Use':True}
2176    if 'Chiral' not in restrData:
2177        restrData['Chiral'] = {'wtFactor':1.0,'Volumes':[],'Use':True}
2178    if 'Torsion' not in restrData:
2179        restrData['Torsion'] = {'wtFactor':1.0,'Coeff':{},'Torsions':[],'Use':True}
2180    if 'Rama' not in restrData:
2181        restrData['Rama'] = {'wtFactor':1.0,'Coeff':{},'Ramas':[],'Use':True}
2182    if 'Texture' not in restrData:
2183        restrData['Texture'] = {'wtFactor':1.0,'HKLs':[],'Use':True}
2184    if 'ChemComp' not in restrData:
2185        restrData['ChemComp'] = {'wtFactor':1.0,'Sites':[],'Use':True}
2186    if 'General' not in restrData:
2187        restrData['General'] = {'wtFactor':1.0,'General':[], 'Use':True}
2188    General = phasedata['General']
2189    Cell = General['Cell'][1:7]          #skip flag & volume   
2190    Amat,Bmat = G2lat.cell2AB(Cell)
2191    SGData = General['SGData']
2192    cx,ct,cs,cia = General['AtomPtrs']
2193    Atoms = phasedata['Atoms']
2194    AtLookUp = G2mth.FillAtomLookUp(Atoms,cia+8)
2195    if 'macro' in General['Type']:
2196        Names = [atom[0]+':'+atom[1]+atom[2]+' '+atom[3].ljust(4) for atom in Atoms]
2197        Ids = []
2198        Coords = []
2199        Types = []
2200        iBeg = 0
2201    else:   
2202        Names = ['all '+ name for name in General['AtomTypes']]
2203        iBeg = len(Names)
2204        Types = [name for name in General['AtomTypes']]
2205        Coords = [ [] for type in Types]
2206        Ids = [ 0 for type in Types]
2207        Names += [atom[ct-1] for atom in Atoms]
2208    Types += [atom[ct] for atom in Atoms]
2209    Coords += [atom[cx:cx+3] for atom in Atoms]
2210    Ids += [atom[cia+8] for atom in Atoms]
2211    rama = G2data.ramachandranDist['All']
2212    ramaName = 'All'
2213    G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.RestraintMenu)   
2214    G2frame.Bind(wx.EVT_MENU, OnAddRestraint, id=G2G.wxID_RESTRAINTADD)
2215    G2frame.Bind(wx.EVT_MENU, OnUseMogul, id=G2G.wxID_USEMOGUL)
2216    G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_USEMOGUL,True)
2217    if 'macro' in phasedata['General']['Type']:
2218        G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_AARESTRAINTADD,True)
2219        G2frame.Bind(wx.EVT_MENU, OnAddAARestraint, id=G2G.wxID_AARESTRAINTADD)
2220        G2frame.Bind(wx.EVT_MENU, OnPlotAARestraint, id=G2G.wxID_AARESTRAINTPLOT)
2221   
2222    # GUI defined here
2223    G2frame.restrBook = G2G.GSNoteBook(parent=G2frame.dataWindow)
2224    G2frame.dataWindow.GetSizer().Add(G2frame.restrBook,1,wx.ALL|wx.EXPAND,1)
2225    # clear menu and menu pointers
2226    Pages = []   
2227
2228    txt = 'Bond'
2229    BondRestr = wx.ScrolledWindow(G2frame.restrBook)
2230    G2frame.restrBook.AddPage(BondRestr,txt)
2231    Pages.append(txt)
2232
2233    txt = 'Angle'
2234    AngleRestr = wx.ScrolledWindow(G2frame.restrBook)
2235    G2frame.restrBook.AddPage(AngleRestr,txt) 
2236    Pages.append(txt)
2237   
2238    txt = 'Plane'
2239    PlaneRestr = wx.ScrolledWindow(G2frame.restrBook)
2240    G2frame.restrBook.AddPage(PlaneRestr,txt)
2241    Pages.append(txt)
2242
2243    txt = 'Chiral'
2244    ChiralRestr = wx.ScrolledWindow(G2frame.restrBook)
2245    G2frame.restrBook.AddPage(ChiralRestr,txt)
2246    Pages.append(txt)
2247
2248    if 'macro' in General['Type']:
2249        txt = 'Torsion'
2250        TorsionRestr = wx.ScrolledWindow(G2frame.restrBook)
2251        G2frame.restrBook.AddPage(TorsionRestr,txt)
2252        Pages.append(txt)
2253
2254        txt = 'Ramachandran'
2255        RamaRestr = wx.ScrolledWindow(G2frame.restrBook)
2256        G2frame.restrBook.AddPage(RamaRestr,txt)
2257        Pages.append(txt)
2258
2259    txt = 'Chem. comp.'
2260    ChemCompRestr = wx.ScrolledWindow(G2frame.restrBook)
2261    G2frame.restrBook.AddPage(ChemCompRestr,txt)
2262    Pages.append(txt)
2263   
2264    txt = 'General'
2265    GeneralRestr = wx.ScrolledWindow(G2frame.restrBook)
2266    G2frame.restrBook.AddPage(GeneralRestr,txt)
2267    Pages.append(txt)
2268   
2269    if General['SH Texture']['Order']:
2270        txt = 'Texture'
2271        TextureRestr = wx.ScrolledWindow(G2frame.restrBook)
2272        G2frame.restrBook.AddPage(TextureRestr,txt)
2273        Pages.append(txt)
2274
2275    UpdateBondRestr(restrData['Bond'])
2276
2277    G2frame.restrBook.Bind(wx.aui.EVT_AUINOTEBOOK_PAGE_CHANGED, OnPageChanged)
2278
2279    # fill page selection menu
2280    menu = G2frame.dataWindow.RestraintTab
2281    for page in Pages:
2282        if menu.FindItem(page) >= 0: continue # is tab already in menu?
2283        Id = wx.NewId()
2284        TabSelectionIdDict[Id] = page
2285        menu.Append(Id,page,'')
2286        G2frame.Bind(wx.EVT_MENU, OnSelectPage, id=Id)
Note: See TracBrowser for help on using the repository browser.