# -*- coding: utf-8 -*-
#GSASIIrestr - restraint GUI routines
########### SVN repository information ###################
# $Date: 2019-08-02 13:33:41 -0500 (Fri, 02 Aug 2019) $
# $Author: vondreele $
# $Revision: 4076 $
# $URL: https://subversion.xray.aps.anl.gov/pyGSAS/trunk/GSASIIrestrGUI.py $
# $Id: GSASIIrestrGUI.py 4076 2019-08-02 18:33:41Z vondreele $
########### SVN repository information ###################
'''
*GSASIIrestrGUI: Restraint GUI routines*
----------------------------------------

Used to define restraints.

'''
from __future__ import division, print_function
import wx
import wx.grid as wg
import numpy as np
import numpy.ma as ma
import os.path
import GSASIIpath
GSASIIpath.SetVersionNumber("$Revision: 4076 $")
import GSASIImath as G2mth
import GSASIIlattice as G2lat
import GSASIIspc as G2spc
import GSASIIdataGUI as G2gd
import GSASIIplot as G2plt
import GSASIIdata as G2data
import GSASIIctrlGUI as G2G
import GSASIIphsGUI as G2phsGUI
import GSASIIobj as G2obj
import GSASIIconstrGUI as G2cnstG
import GSASIIexprGUI as G2exG

try:
    wx.NewIdRef
    wx.NewId = wx.NewIdRef
except AttributeError:
    pass

WACV = wx.ALIGN_CENTER_VERTICAL
VERY_LIGHT_GREY = wx.Colour(235,235,235)
TabSelectionIdDict = {}

################################################################################
#####  Restraints
################################################################################           
def GetSelectedRows(widget):
    '''Returns a list of selected rows. Rows can be selected, blocks of cells
    or individual cells can be selected. The column for selected cells is ignored.
    '''
    try:
        rows = widget.GetSelectedRows()
    except:
        return
    if not rows:
        top = widget.GetSelectionBlockTopLeft()
        bot = widget.GetSelectionBlockBottomRight()
        if top and bot:
            rows = range(top[0][0],bot[0][0]+1)
    if not rows:
        rows = sorted(list(set([cell[0] for cell in widget.GetSelectedCells()])))
    return rows
       
def UpdateRestraints(G2frame,data,phaseName):
    '''Respond to selection of the Restraints item on the
    data tree
    '''
#    global Pages
    
    def getMacroFile(macName):
        defDir = os.path.join(os.path.split(__file__)[0],'GSASIImacros')
        dlg = wx.FileDialog(G2frame,message='Choose '+macName+' restraint macro file',
            defaultDir=defDir,defaultFile="",wildcard="GSAS-II macro file (*.mac)|*.mac",
            style=wx.FD_OPEN | wx.FD_CHANGE_DIR)
        try:
            macro = ''
            if dlg.ShowModal() == wx.ID_OK:
                macfile = dlg.GetPath()
                macro = open(macfile,'Ur')
                head = macro.readline()
                if macName not in head:
                    print (head)
                    print ('**** ERROR - wrong restraint macro file selected, try again ****')
                    macro = []
        finally:
            dlg.Destroy()
        return macro        #advanced past 1st line
        
    def getMOGULFile():
        dlg = wx.FileDialog(G2frame,message='Choose MOGUL csv file',
            defaultDir='.',defaultFile="",wildcard="MOGUL csv file (*.csv)|*.csv",
            style=wx.FD_OPEN | wx.FD_CHANGE_DIR)
        try:
            mogul = ''
            if dlg.ShowModal() == wx.ID_OK:
                csvfile = dlg.GetPath()
                mogul = open(csvfile,'Ur')
                head = mogul.readline()
                if 'Type' not in head:
                    print (head)
                    print ('**** ERROR - not a MOGUL csv file selected, try again ****')
                    mogul = []
        finally:
            dlg.Destroy()
        return mogul        #advanced past 1st line
        
    def OnPlotAARestraint(event):
        page = G2frame.restrBook.GetSelection()
        if 'Torsion' in G2frame.restrBook.GetPageText(page):
            torNames = []
            torNames += list(restrData['Torsion']['Coeff'].keys())
            dlg = wx.SingleChoiceDialog(G2frame,'Select','Torsion data',torNames)
            try:
                if dlg.ShowModal() == wx.ID_OK:
                    torName = torNames[dlg.GetSelection()]
                    torsion = G2data.torsionDist[torName]
                    torCoeff = restrData['Torsion']['Coeff'][torName]
                    torList = restrData['Torsion']['Torsions']
                    Names = []
                    Angles = []
                    for i,[indx,ops,cofName,esd] in enumerate(torList):
                        if cofName == torName:
                            atoms = G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,0,4)
                            name = '('+atoms[2][1]+atoms[2][0].strip()+atoms[2][2]+')'
                            for atom in atoms:
                                name += '  '+atom[3]
                            XYZ = np.array(G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,cx,3))
                            angle = G2mth.getRestTorsion(XYZ,Amat)
                            Angles.append(angle)
                            Names.append(name) 
                    G2plt.PlotTorsion(G2frame,phaseName,torsion,torName,Names,np.array(Angles),torCoeff)
            finally:
                dlg.Destroy()
            
        elif 'Rama' in G2frame.restrBook.GetPageText(page):
            ramaNames = ['All',]
            ramaNames += list(restrData['Rama']['Coeff'].keys())
            dlg = wx.SingleChoiceDialog(G2frame,'Select','Ramachandran data',ramaNames)
            try:
                if dlg.ShowModal() == wx.ID_OK:
                    ramaName = ramaNames[dlg.GetSelection()]
                    rama = G2data.ramachandranDist[ramaName]
                    ramaCoeff = []
                    if ramaName != 'All':
                        ramaCoeff = restrData['Rama']['Coeff'][ramaName]
                    ramaList = restrData['Rama']['Ramas']
                    Names = []
                    PhiPsi = []
                    for i,[indx,ops,cofName,esd] in enumerate(ramaList):
                        if cofName == ramaName or (ramaName == 'All' and '-1' in cofName):
                            atoms = G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,0,4)
                            name = '('+atoms[3][1]+atoms[3][0].strip()+atoms[3][2]+')'
                            for atom in atoms:
                                name += '  '+atom[3]
                            XYZ = np.array(G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,cx,3))
                            phi,psi = G2mth.getRestRama(XYZ,Amat)
                            PhiPsi.append([phi,psi])
                            Names.append(name) 
                    G2plt.PlotRama(G2frame,phaseName,rama,ramaName,Names,np.array(PhiPsi),ramaCoeff)
            finally:
                dlg.Destroy()

    def SetupParmDict(G2frame):
        '''Creates a parameter dict with variable names as keys and 
        numerical values (only)
        '''
        G2cnstG.CheckAllScalePhaseFractions(G2frame)
        try:
            parmDict,varyList = G2frame.MakeLSParmDict()
        except:
            print('Error retrieving parameters')
            return {}
        return {i:parmDict[i][0] for i in parmDict}
        
    def OnAddRestraint(event):
        '''Adds a restraint depending on which tab is currently displayed'''
        page = G2frame.restrBook.GetSelection()
        if 'Bond' in G2frame.restrBook.GetPageText(page):
            AddBondRestraint(restrData['Bond'])
        elif 'Angle' in G2frame.restrBook.GetPageText(page):
            AddAngleRestraint(restrData['Angle'])
        elif 'Plane' in G2frame.restrBook.GetPageText(page):
            AddPlaneRestraint(restrData['Plane'])
        elif 'Chem' in G2frame.restrBook.GetPageText(page):
            AddChemCompRestraint(restrData['ChemComp'])
        elif 'Texture' in G2frame.restrBook.GetPageText(page):
            AddTextureRestraint(restrData['Texture'])
        elif 'General' in G2frame.restrBook.GetPageText(page):
            parmDict = SetupParmDict(G2frame)
            dlg = G2exG.ExpressionDialog(G2frame,parmDict,
                           header="Create a restraint expression",
                           fit=False,wildCard=G2frame.testSeqRefineMode())
            restobj = dlg.Show(True)
            if restobj:
                restrData['General']['General'].append([restobj,0.0,1.0])
                wx.CallAfter(UpdateGeneralRestr,restrData['General'])
            
    def OnAddAARestraint(event):
        page = G2frame.restrBook.GetSelection()
        if 'Bond' in G2frame.restrBook.GetPageText(page):
            AddAABondRestraint(restrData['Bond'])
        elif 'Angle' in G2frame.restrBook.GetPageText(page):
            AddAAAngleRestraint(restrData['Angle'])
        elif 'Plane' in G2frame.restrBook.GetPageText(page):
            AddAAPlaneRestraint(restrData['Plane'])
        elif 'Chiral' in G2frame.restrBook.GetPageText(page):
            AddAAChiralRestraint(restrData['Chiral'])
        elif 'Torsion' in G2frame.restrBook.GetPageText(page):
            AddAATorsionRestraint(restrData['Torsion'])
        elif 'Rama' in G2frame.restrBook.GetPageText(page):
            AddAARamaRestraint(restrData['Rama'])
            
    def OnUseMogul(event):
        page = G2frame.restrBook.GetSelection()
        if 'Bond' in G2frame.restrBook.GetPageText(page):
            AddMogulBondRestraint(restrData['Bond'])
        elif 'Angle' in G2frame.restrBook.GetPageText(page):
            AddMogulAngleRestraint(restrData['Angle'])
            
    def makeChains(Names,Ids):
        Chains = {}
        atoms = zip(Names,Ids)
        for name,Id in atoms:
            items = name.split(' ',2)
            rnum,res = items[0].split(':')
            rnum = int(rnum)
            if items[1] not in Chains:
                Residues = {}
                Chains[items[1]] = Residues
            if rnum not in Residues:
                Residues[rnum] = [[],[]]
            if items[2][3] in [' ','A']:
                Residues[rnum][0].append([res,items[2],Id])
            if items[2][3] in [' ','B']:
                Residues[rnum][1].append([res,items[2],Id])
        return Chains
        
    def AddBondRestraint(bondRestData):
        Lists = {'origin':[],'target':[]}
        for listName in ['origin','target']:
            dlg = wx.MultiChoiceDialog(G2frame,'Bond restraint '+listName+' for '+General['Name'],
                    'Select bond restraint '+listName+' atoms',Names)
            if dlg.ShowModal() == wx.ID_OK:
                sel = dlg.GetSelections()
                for x in sel:
                    if 'all' in Names[x]:
                        allType = Types[x]
                        for name,Type,coords,Id in zip(Names,Types,Coords,Ids):
                            if Type == allType and 'all' not in name:
                                Lists[listName].append([Id,Type,coords])
                    else:
                        Lists[listName].append([Ids[x],Types[x],Coords[x],])
            else:
                break
        if len(Lists['origin']) and len(Lists['target']):
            bond = 1.54
            dlg = G2G.SingleFloatDialog(G2frame,'Distance','Enter restraint distance for bond',bond,[0.01,4.],'%.4f')
            if dlg.ShowModal() == wx.ID_OK:
                bond = dlg.GetValue()
            dlg.Destroy()
        Factor = bondRestData['Range']
        indices = (-2,-1,0,1,2)
        Units = np.array([[h,k,l] for h in indices for k in indices for l in indices])
        origAtoms = Lists['origin']
        targAtoms = Lists['target']
        dlg = wx.ProgressDialog("Generating bond restraints","Processed origin atoms",len(origAtoms), 
            style = wx.PD_ELAPSED_TIME|wx.PD_AUTO_HIDE|wx.PD_REMAINING_TIME)
        try:
            Norig = 0
            for Oid,Otype,Ocoord in origAtoms:
                Norig += 1
                dlg.Update(Norig)
                for Tid,Ttype,Tcoord in targAtoms:
                    if 'macro' in General['Type']:
                        result = [[Tcoord,1,[0,0,0]],]
                    else:
                        result = G2spc.GenAtom(Tcoord,SGData,False,Move=False)
                    for Txyz,Top,Tunit,Spn in result:
                        Dx = (Txyz-np.array(Ocoord))+Units
                        dx = np.inner(Amat,Dx)
                        dist = ma.masked_less(np.sqrt(np.sum(dx**2,axis=0)),bond/Factor)
                        IndB = ma.nonzero(ma.masked_greater(dist,bond*Factor))
                        if np.any(IndB):
                            for indb in IndB:
                                for i in range(len(indb)):
                                    unit = Units[indb][i]+Tunit
                                    if np.any(unit):
                                        Topstr = '%d+%d,%d,%d'%(Top,unit[0],unit[1],unit[2])
                                    else:
                                        Topstr = str(Top)
                                    newBond = [[Oid,Tid],['1',Topstr],bond,0.01]
                                    if newBond not in bondRestData['Bonds']:
                                        bondRestData['Bonds'].append(newBond)
        finally:
            dlg.Destroy()
        UpdateBondRestr(bondRestData)                

    def AddAABondRestraint(bondRestData):
        macro = getMacroFile('bond')
        if not macro:
            return
        macStr = macro.readline()
        atoms = zip(Names,Coords,Ids)
        Factor = bondRestData['Range']
        while macStr:
            items = macStr.split()
            if 'F' in items[0]:
                restrData['Bond']['wtFactor'] = float(items[1])
            elif 'S' in items[0]:
                oIds = []
                oCoords = []
                oDis = []
                tIds = []
                tCoords = []
                tDis = []
                res = items[1]
                dist = float(items[2])
                esd = float(items[3])
                oAtm,tAtm = items[4:6]
                for Name,coords,Id in atoms:
                    names = Name.split(' ',2)
                    if res == '*' or res in names[0]:
                        if oAtm.ljust(3) == names[2][:3]:
                            oIds.append(Id)
                            oCoords.append(np.array(coords))
                            oDis.append(names[2][3])
                        if tAtm.ljust(3) == names[2][:3]:
                            tIds.append(Id)
                            tCoords.append(np.array(coords))
                            tDis.append(names[2][3])
                for i,[oId,oCoord,odis] in enumerate(zip(oIds,oCoords,oDis)):
                    for tId,tCoord,tdis in zip(tIds,tCoords,tDis)[i:]:
                        if odis+tdis in ['AB','BA']:
                            continue
                        obsd = np.sqrt(np.sum(np.inner(Amat,tCoord-oCoord)**2))
                        if dist/Factor < obsd < dist*Factor:
                            newBond = [[oId,tId],['1','1'],dist,esd]
                            if newBond not in bondRestData['Bonds']:
                                bondRestData['Bonds'].append(newBond)              
            macStr = macro.readline()
        macro.close()
        print(' Found %d bond restraints'%len(bondRestData['Bonds']))
        UpdateBondRestr(bondRestData)

    def AddMogulBondRestraint(bondRestData):
        mogul = getMOGULFile()
        for line in mogul:
            items = line.split(',')
            if 'bond' == items[0]:
                oName,tName = items[2].split()
                oInd = Names.index(oName)
                tInd = Names.index(tName)
                if items[3] != 'No hits':
                    dist = float(items[6])
                    esd = float(items[7])
                else:
                    dist = float(items[5])
                    esd = 0.02
                newBond = [[Ids[oInd],Ids[tInd]],['1','1'],dist,esd]
                if newBond not in bondRestData['Bonds']:
                    bondRestData['Bonds'].append(newBond)              
        UpdateBondRestr(bondRestData)
            
    def AddAngleRestraint(angleRestData):
        Radii = dict(zip(General['AtomTypes'],zip(General['BondRadii'],General['AngleRadii'])))
        Lists = {'A-atom':[],'B-atom':[],'C-atom':[]}
        for listName in ['A-atom','B-atom']:
            dlg = wx.MultiChoiceDialog(G2frame,'Select '+listName+' for angle A-B-C for '+General['Name']                                                                           ,
                    'Select angle restraint '+listName,Names)
            if dlg.ShowModal() == wx.ID_OK:
                sel = dlg.GetSelections()
                for x in sel:
                    if 'all' in Names[x]:
                        allType = Types[x]
                        for name,Type,coords,Id in zip(Names,Types,Coords,Ids):
                            if Type == allType and 'all' not in name:
                                if 'A' in listName:
                                    Lists[listName].append(Type)
                                else:
                                    Lists[listName].append([Id,Type,coords])
                    else:
                        if 'A' in listName:
                            Lists[listName].append(Types[x])
                        else:
                            Lists[listName].append([Ids[x],Types[x],Coords[x],])
            else:
                break
            targAtoms = [[Ids[x+iBeg],Types[x+iBeg],Coords[x+iBeg]] for x in range(len(Names[iBeg:]))]
        if len(Lists['B-atom']):
            value = 109.54
            dlg = G2G.SingleFloatDialog(G2frame,'Angle','Enter restraint angle ',value,[30.,180.],'%.2f')
            if dlg.ShowModal() == wx.ID_OK:
                value = dlg.GetValue()
            dlg.Destroy()

        Factor = angleRestData['Range']
        indices = (-2,-1,0,1,2)
        Units = np.array([[h,k,l] for h in indices for k in indices for l in indices])
        VectA = []
        for Oid,Otype,Ocoord in Lists['B-atom']:
            IndBlist = []
            VectB = []
            for Tid,Ttype,Tcoord in targAtoms:
                result = G2spc.GenAtom(Tcoord,SGData,False,Move=False)
                BsumR = (Radii[Otype][0]+Radii[Ttype][0])*Factor
                AsumR = (Radii[Otype][1]+Radii[Ttype][1])*Factor
                for Txyz,Top,Tunit,Spn in result:
                    Dx = (Txyz-Ocoord)+Units
                    dx = np.inner(Amat,Dx)
                    dist = ma.masked_less(np.sqrt(np.sum(dx**2,axis=0)),0.5)
                    IndB = ma.nonzero(ma.masked_greater(dist,BsumR))
                    if np.any(IndB):
                        for indb in IndB:
                            for i in range(len(indb)):
                                if str(dx.T[indb][i]) not in IndBlist:
                                    IndBlist.append(str(dx.T[indb][i]))
                                    unit = Units[indb][i]+Tunit
                                if np.any(unit):
                                    Topstr = '%d+%d,%d,%d'%(Top,unit[0],unit[1],unit[2])
                                else:
                                    Topstr = str(Top)
                                Dist = ma.getdata(dist[indb])[i]
                                if (Dist-AsumR) <= 0.:
                                    VectB.append([Oid,'1',Ocoord,Ttype,Tid,Topstr,Tcoord,Dist])
            VectA.append(VectB)
        for Vects in VectA:
            for i,vecta in enumerate(Vects):                    
                for vectb in Vects[:i]:
                    if vecta[3] in Lists['A-atom']:
                        ids = [vecta[4],vecta[0],vectb[4]]
                        ops = [vecta[5],vecta[1],vectb[5]]
                        angle = [ids,ops,value,1.0]
                        if angle not in angleRestData['Angles']:
                            angleRestData['Angles'].append(angle)
        UpdateAngleRestr(angleRestData)                

    def AddAAAngleRestraint(angleRestData):
        macro = getMacroFile('angle')
        if not macro:
            return
        Chains = makeChains(Names,Ids)            
        macStr = macro.readline()
        while macStr:
            items = macStr.split()
            if 'F' in items[0]:
                restrData['Angle']['wtFactor'] = float(items[1])
            elif 'S' in items[0]:
                Res = items[1]
                Value = float(items[2])
                Esd = float(items[3])
                Atms = items[4:7]
                pAtms = ['','','']
                for i,atm in enumerate(Atms):
                    if '+' in atm:
                        pAtms[i] = atm.strip('+')
                ids = [0,0,0]
                chains = list(Chains.keys())
                chains.sort()
                for chain in chains:
                    residues = list(Chains[chain].keys())
                    residues.sort()
                    for residue in residues:
                        for ires in [0,1]:
                            if Res != '*':  #works with disordered res
                                for res,name,Id in Chains[chain][residue][ires]:
                                    if Res == res:
                                        try:
                                            ipos = Atms.index(name[:3].strip())
                                            ids[ipos] = Id
                                        except ValueError:
                                            continue
                            else:
                                try:
                                    for res,name,Id in Chains[chain][residue][ires]:
                                        try:
                                            ipos = Atms.index(name[:3].strip())
                                            ids[ipos] = Id
                                        except ValueError:
                                            continue
                                    for res,name,Id in Chains[chain][residue+1][ires]:
                                        try:
                                            ipos = pAtms.index(name[:3].strip())
                                            ids[ipos] = Id
                                        except ValueError:
                                            continue
                                except KeyError:
                                    continue
                            if all(ids):
                                angle = [list(ids),['1','1','1'],Value,Esd]
                                if angle not in angleRestData['Angles']:
                                    angleRestData['Angles'].append(angle)
                                ids = [0,0,0]
            macStr = macro.readline()
        macro.close()
        print(' Found %d angle restraints'%len(angleRestData['Angles']))
        UpdateAngleRestr(angleRestData)                
        
    def AddMogulAngleRestraint(angleRestData):
        mogul = getMOGULFile()
        for line in mogul:
            items = line.split(',')
            if 'angle' == items[0]:
                aName,bName,cName = items[2].split()
                aInd = Names.index(aName)
                bInd = Names.index(bName)
                cInd = Names.index(cName)
                if items[3] != 'No hits':
                    angle = float(items[6])
                    esd = float(items[7])
                else:
                    angle = float(items[5])
                    esd = 2.00
                newAngle = [[Ids[aInd],Ids[bInd],Ids[cInd]],['1','1','1'],angle,esd]
                if newAngle not in angleRestData['Angles']:
                    angleRestData['Angles'].append(newAngle)              
        UpdateAngleRestr(angleRestData)                

    def AddPlaneRestraint(restrData):
        ids = []
        dlg = wx.MultiChoiceDialog(G2frame,'Select 4 or more atoms for plane in '+General['Name'],
                'Select 4+ atoms',Names[iBeg:])
        if dlg.ShowModal() == wx.ID_OK:
            sel = dlg.GetSelections()
            if len(sel) > 3:
                for x in sel:
                    ids.append(Ids[x+iBeg])
                ops = ['1' for i in range(len(sel))]
                plane = [ids,ops,0.0,0.01]
                if plane not in restrData['Planes']:
                    restrData['Planes'].append(plane)
            else:
                print ('**** ERROR - not enough atoms for a plane restraint - try again ****')
        UpdatePlaneRestr(restrData)                

    def AddAAPlaneRestraint(planeRestData):
        macro = getMacroFile('plane')
        if not macro:
            return
        Chains = makeChains(Names,Ids)            
        macStr = macro.readline()
        while macStr:
            items = macStr.split()
            if 'F' in items[0]:
                restrData['Plane']['wtFactor'] = float(items[1])
            elif 'S' in items[0]:
                Res = items[1]
                Esd = float(items[2])
                Atms = items[3:]
                pAtms = ['' for i in Atms]
                for i,atm in enumerate(Atms):
                    if '+' in atm:
                        pAtms[i] = atm.strip('+')
                ids = [0,]*len(Atms)
                ops = ['1' for i in range(len(Atms))]
                chains = list(Chains.keys())
                chains.sort()
                for chain in chains:
                    residues = list(Chains[chain].keys())
                    residues.sort()
                    for residue in residues:
                        for ires in [0,1]:
                            if residue == residues[-1] and Res == '*':
                                continue
                            if Res != '*':  #works with disordered res
                                for res,name,Id in Chains[chain][residue][ires]:
                                    if Res == res:
                                        try:
                                            ipos = Atms.index(name[:3].strip())
                                            ids[ipos] = Id
                                        except ValueError:
                                            continue
                            else:
                                try:
                                    for res,name,Id in Chains[chain][residue][ires]:
                                        try:
                                            ipos = Atms.index(name[:3].strip())
                                            ids[ipos] = Id
                                        except ValueError:
                                            continue
                                    for res,name,Id in Chains[chain][residue+1][ires]:
                                        try:
                                            ipos = pAtms.index(name[:3].strip())
                                            ids[ipos] = Id
                                        except ValueError:
                                            continue
                                except KeyError:
                                    continue
                            if all(ids):
                                plane = [list(ids),ops,0.0,Esd]
                                if plane not in planeRestData['Planes']:
                                    planeRestData['Planes'].append(plane)
                                ids = [0,]*len(Atms)
            macStr = macro.readline()
        macro.close()
        print(' Found %d plane restraints'%len(planeRestData['Planes']))
        UpdatePlaneRestr(planeRestData)                

    def AddAAChiralRestraint(chiralRestData):
        macro = getMacroFile('chiral')
        if not macro:
            return
        Chains = makeChains(Names,Ids)            
        macStr = macro.readline()
        while macStr:
            items = macStr.split()
            if 'F' in items[0]:
                restrData['Chiral']['wtFactor'] = float(items[1])
            elif 'S' in items[0]:
                Res = items[1]
                Value = float(items[2])
                Esd = float(items[3])
                Atms = items[4:8]
                ids = [0,0,0,0]
                chains = list(Chains.keys())
                chains.sort()
                for chain in chains:
                    residues = list(Chains[chain].keys())
                    residues.sort()
                    for residue in residues:
                        for ires in [0,1]:
                            if residue == residues[-1] and Res == '*':
                                continue
                            if Res != '*':  #works with disordered res
                                for res,name,Id in Chains[chain][residue][ires]:
                                    if Res == res:
                                        try:
                                            ipos = Atms.index(name[:3].strip())
                                            ids[ipos] = Id
                                        except ValueError:
                                            continue
                            else:
                                try:
                                    for res,name,Id in Chains[chain][residue][ires]:
                                        try:
                                            ipos = Atms.index(name[:3].strip())
                                            ids[ipos] = Id
                                        except ValueError:
                                            continue
                                except KeyError:
                                    continue
                            if all(ids):
                                chiral = [list(ids),['1','1','1','1'],Value,Esd]
                                if chiral not in chiralRestData['Volumes']:
                                    chiralRestData['Volumes'].append(chiral)
                                ids = [0,0,0,0]
            macStr = macro.readline()
        macro.close()
        print(' Found %d chiral volumes restraints'%len(chiralRestData['Volumes']))
        UpdateChiralRestr(chiralRestData)                
        
    def AddAATorsionRestraint(torsionRestData):
        macro = getMacroFile('torsion')
        if not macro:
            return
        Chains = makeChains(Names,Ids)            
        macStr = macro.readline()[:-1]
        while macStr:
            items = macStr.split()
            if 'F' in items[0]:
                restrData['Torsion']['wtFactor'] = float(items[1])
            elif 'A' in items[0]:
                name = items[10]
                coeff = np.zeros(9)
                for i,item in enumerate(items[1:10]):
                    coeff[i] = float(item)
                torsionRestData['Coeff'][name] = coeff
            elif 'S' in items[0]:
                Name = items[1]
                Res = items[2]
                Esd = float(items[3])
                Atms = items[4:8]
                pAtms = ['','','','']
                for i,atm in enumerate(Atms):
                    if '+' in atm:
                        pAtms[i] = atm.strip('+')
                ids = [0,0,0,0]
                chains = list(Chains.keys())
                chains.sort()
                for chain in chains:
                    residues = list(Chains[chain].keys())
                    residues.sort()
                    for residue in residues:
                        for ires in [0,1]:
                            if residue == residues[-1] and Res == '*':
                                continue
                            if Res != '*':  #works with disordered res
                                for res,name,Id in Chains[chain][residue][ires]:
                                    if Res == res:
                                        try:
                                            ipos = Atms.index(name[:3].strip())
                                            ids[ipos] = Id
                                        except ValueError:
                                            continue
                            else:
                                try:
                                    for res,name,Id in Chains[chain][residue][ires]:
                                        try:
                                            ipos = Atms.index(name[:3].strip())
                                            ids[ipos] = Id
                                        except ValueError:
                                            continue
                                    for res,name,Id in Chains[chain][residue+1][ires]:
                                        try:
                                            ipos = pAtms.index(name[:3].strip())
                                            ids[ipos] = Id
                                        except ValueError:
                                            continue
                                except KeyError:
                                    continue
                            if all(ids):
                                torsion = [list(ids),['1','1','1','1'],Name,Esd]
                                if torsion not in torsionRestData['Torsions']:
                                    torsionRestData['Torsions'].append(torsion)
                                ids = [0,0,0,0]                            
            macStr = macro.readline()
        macro.close()
        print(' Found %d torsion restraints'%len(torsionRestData['Torsions']))
        UpdateTorsionRestr(torsionRestData)                        
       
    def AddAARamaRestraint(ramaRestData):
        macro = getMacroFile('Ramachandran')
        if not macro:
            return
        Chains = makeChains(Names,Ids)            
        macStr = macro.readline()
        while macStr:
            items = macStr.split()
            if 'F' in items[0]:
                restrData['Rama']['wtFactor'] = float(items[1])
            elif 'A' in items[0]:
                nTerms = int(items[1])
                name = items[2]
                coeff = np.zeros((nTerms,6))
                for i in range(nTerms):
                    macStr = macro.readline()
                    items = macStr.split()
                    for j,val in enumerate(items):
                        coeff[i][j] = float(val)
                ramaRestData['Coeff'][name] = coeff
            elif 'S' in items[0]:
                Name = items[1]
                Res = items[2]
                Esd = float(items[3])
                Atms = items[4:9]
                mAtms = ['','','','','']
                pAtms = ['','','','','']
                for i,atm in enumerate(Atms):
                    if '+' in atm:
                        pAtms[i] = atm.strip('+')
                    elif '-' in atm:
                        mAtms[i] = atm.strip('-')
                ids = [0,0,0,0,0]
                chains = list(Chains.keys())
                chains.sort()
                for chain in chains:
                    residues = list(Chains[chain].keys())
                    residues.sort()
                    if not (any(mAtms) or any(pAtms)):
                        for residue in residues:
                            for ires in [0,1]:
                                for res,name,Id in Chains[chain][residue][ires]:
                                    if Res == res:
                                        try:
                                            ipos = Atms.index(name[:3].strip())
                                            ids[ipos] = Id
                                        except ValueError:
                                            continue
                                if all(ids):
                                    rama = [list(ids),['1','1','1','1','1'],Name,Esd]
                                    if rama not in ramaRestData['Ramas']:
                                        ramaRestData['Ramas'].append(rama)
                                    ids = [0,0,0,0,0]
                    else:
                        for ires,residue in enumerate(residues[1:-1]):
                            for jres in [0,1]:
                                try:
                                    for res,name,Id in Chains[chain][residue-1][jres]:
                                        try:
                                            ipos = mAtms.index(name[:3].strip())
                                            ids[ipos] = Id
                                        except ValueError:
                                            continue
                                    for res,name,Id in Chains[chain][residue+1][jres]:
                                        try:
                                            ipos = pAtms.index(name[:3].strip())
                                            ids[ipos] = Id
                                        except ValueError:
                                            continue
                                    for res,name,Id in Chains[chain][residue][jres]:
                                        if Res == res:
                                            try:
                                                ipos = Atms.index(name[:3].strip())
                                                ids[ipos] = Id
                                            except ValueError:
                                                continue
                                    if all(ids):
                                        rama = [list(ids),['1','1','1','1','1'],Name,Esd]
                                        if rama not in ramaRestData['Ramas']:
                                            ramaRestData['Ramas'].append(rama)
                                        ids = [0,0,0,0,0]
                                except KeyError:
                                    continue
            macStr = macro.readline()
        macro.close()
        print(' Found %d Ramachandran restraints'%len(ramaRestData['Ramas']))
        UpdateRamaRestr(ramaRestData)
        
    def AddChemCompRestraint(chemcompRestData):
        ids = []
        factors = []
        dlg = wx.MultiChoiceDialog(G2frame,'Select atoms for chemical restraint in '+General['Name'],
                'Select atoms',Names)
        if dlg.ShowModal() == wx.ID_OK:
            sel = dlg.GetSelections()
            for x in sel:
                if 'all' in Names[x]:
                    allType = Types[x]
                    for name,Type,Id in zip(Names,Types,Ids):
                        if Type == allType and 'all' not in name:
                            ids.append(Id)
                            factors.append(1.0)
                else:
                    ids.append(Ids[x])
                    factors.append(1.0)
            dlg.Destroy()
            if len(ids) > 0:
                value = 1.0
                dlg = G2G.SingleFloatDialog(G2frame,'Cell sum','Enter unit cell sum ',value,[-1.e6,1.e6],'%.2f')
                if dlg.ShowModal() == wx.ID_OK:
                    value = dlg.GetValue()                
                    comp = [ids,factors,value,0.01]
                    if comp not in chemcompRestData['Sites']:
                        chemcompRestData['Sites'].append(comp)
                UpdateChemcompRestr(chemcompRestData)
            else:
                print ('**** ERROR - not enough atoms for a composition restraint - try again ****')
        
    def AddTextureRestraint(textureRestData):
        dlg = wx.TextEntryDialog(G2frame,'Enter h k l for pole figure restraint','Enter HKL','')
        if dlg.ShowModal() == wx.ID_OK:
            text = dlg.GetValue()
            vals = text.split()
            try:
                hkl = [int(vals[i]) for i in range(3)]
                texture = [hkl,24,0.01,False,1.0]
                if texture not in textureRestData['HKLs']:
                        textureRestData['HKLs'].append(texture)
                UpdateTextureRestr(textureRestData)
            except (ValueError,IndexError):
                print ('**** ERROR - bad input of h k l - try again ****')
        dlg.Destroy()
               
    def WtBox(wind,restData):
        if 'Range' not in restData: restData['Range'] = 1.1     #patch
        
        def OnUseData(event):
            Obj = event.GetEventObject()
            restData['Use'] = Obj.GetValue()

        wtBox = wx.BoxSizer(wx.HORIZONTAL)
        wtBox.Add(wx.StaticText(wind,-1,'Phase '+phaseName+' Restraint weight factor: '),0,WACV)
        wtfactor = G2G.ValidatedTxtCtrl(wind,restData,'wtFactor',nDig=(10,2),typeHint=float)
        wtBox.Add(wtfactor,0,WACV)
        useData = wx.CheckBox(wind,-1,label=' Use?')
        useData.Bind(wx.EVT_CHECKBOX, OnUseData)
        useData.SetValue(restData['Use'])        
        wtBox.Add(useData,0,WACV)
        if 'Bonds' in restData or 'Angles' in restData:
            wtBox.Add(wx.StaticText(wind,-1,'  Search range: '),0,WACV)
            sRange = G2G.ValidatedTxtCtrl(wind,restData,'Range',nDig=(10,2),typeHint=float)
            wtBox.Add(sRange,0,WACV)
            wtBox.Add(wx.StaticText(wind,-1,' x sum(atom radii)'),0,WACV)
        return wtBox
        
    def OnRowSelect(event):
        r,c =  event.GetRow(),event.GetCol()
        Obj = event.GetEventObject()
        if r < 0 and c < 0:
            if Obj.IsSelection():
                Obj.ClearSelection()
            else:
                for row in range(Obj.GetNumberRows()):
                    Obj.SelectRow(row,True)
        elif c < 0:                   #only row clicks
            if event.ControlDown():                    
                if r in Obj.GetSelectedRows():
                    Obj.DeselectRow(r)
                else:
                    Obj.SelectRow(r,True)
            elif event.ShiftDown():
                indxs = Obj.GetSelectedRows()
                Obj.ClearSelection()
                ibeg = 0
                if indxs:
                    ibeg = indxs[-1]
                for row in range(ibeg,r+1):
                    Obj.SelectRow(row,True)
            else:
                Obj.ClearSelection()
                Obj.SelectRow(r,True)
                
                    
    def UpdateBondRestr(bondRestData):
        
        def OnCellChange(event):
            r,c =  event.GetRow(),event.GetCol()
            try:
                new = float(bondTable.GetValue(r,c))
                if new <= 0.:
                    raise ValueError
                bondRestData['Bonds'][r][c] = new
            except ValueError:
                pass            
            wx.CallAfter(UpdateBondRestr,bondRestData)                

        def OnChangeValue(event):
            rows = GetSelectedRows(Bonds)
            if not rows:
                return
            Bonds.ClearSelection()
            val = bondList[rows[0]][2]
            dlg = G2G.SingleFloatDialog(G2frame,'New value','Enter new value for bond',val,[0.,5.],'%.4f')
            if dlg.ShowModal() == wx.ID_OK:
                parm = dlg.GetValue()
                for r in rows:
                    bondRestData['Bonds'][r][2] = parm
            dlg.Destroy()
            UpdateBondRestr(bondRestData)                

        def OnChangeEsd(event):
            rows = GetSelectedRows(Bonds)
            if not rows:
                return
            Bonds.ClearSelection()
            val = bondList[rows[0]][3]
            dlg = G2G.SingleFloatDialog(G2frame,'New value','Enter new esd for bond',val,[0.,1.],'%.4f')
            if dlg.ShowModal() == wx.ID_OK:
                parm = dlg.GetValue()
                for r in rows:
                    bondRestData['Bonds'][r][3] = parm
            dlg.Destroy()
            UpdateBondRestr(bondRestData)                
                                
        def OnDeleteRestraint(event):
            rows = GetSelectedRows(Bonds)
            G2frame.GetStatusBar().SetStatusText('',1)
            if not rows:
                G2frame.GetStatusBar().SetStatusText('First select restraints to be deleted',1)
                return
            Bonds.ClearSelection()
            rows.sort()
            rows.reverse()
            for row in rows:
                bondList.remove(bondList[row])
            UpdateBondRestr(bondRestData)                
            
        if BondRestr.GetSizer(): BondRestr.GetSizer().Clear(True)
        mainSizer = wx.BoxSizer(wx.VERTICAL)
        mainSizer.Add((5,5),0)
        mainSizer.Add(WtBox(BondRestr,bondRestData),0,WACV)

        bondList = bondRestData['Bonds']
        if len(bondList) and len(bondList[0]) == 6:   #patch
            bondList = bondRestData['Bonds'] = []
        if len(bondList):
            table = []
            rowLabels = []
            bad = []
            chisq = 0.
            Types = [wg.GRID_VALUE_STRING,]+4*[wg.GRID_VALUE_FLOAT+':10,3',]
            if 'macro' in General['Type']:
                colLabels = ['(res) A - (res) B','calc','target','esd','delt/sig']
                for i,[indx,ops,obs,esd] in enumerate(bondList):
                    try:
                        atoms = G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,0,4)
                        name = ''
                        for atom in atoms:
                            name += '('+atom[1]+atom[0].strip()+atom[2]+') '+atom[3]+' - '
                        XYZ = np.array(G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,cx,3))
                        calc = G2mth.getRestDist(XYZ,Amat)
                        chisq += bondRestData['wtFactor']*((obs-calc)/esd)**2
                        table.append([name[:-3],calc,obs,esd,(obs-calc)/esd])
                        rowLabels.append(str(i))                
                    except KeyError:
                        print ('**** WARNING - missing atom - restraint deleted ****')
                        bad.append(i)
            else:
                colLabels = ['A+SymOp - B+SymOp','calc','target','esd','delt/sig']
                for i,[indx,ops,obs,esd] in enumerate(bondList):
                    try:
                        names = G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,ct-1)
                        XYZ = np.array(G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,cx,3))
                        XYZ = G2mth.getSyXYZ(XYZ,ops,SGData)
                        calc = G2mth.getRestDist(XYZ,Amat)
                        chisq += bondRestData['wtFactor']*((obs-calc)/esd)**2
                        table.append([names[0]+'+('+ops[0]+') - '+names[1]+'+('+ops[1]+')',calc,obs,esd,(obs-calc)/esd])
                        rowLabels.append(str(i))
                    except KeyError:
                        print ('**** WARNING - missing atom - restraint deleted ****')
                        bad.append(i)
            if len(bad):
                bad.reverse()
                for ibad in bad:
                    del bondList[ibad]
            if len(bondList):
                bondTable = G2G.Table(table,rowLabels=rowLabels,colLabels=colLabels,types=Types)
                Bonds = G2G.GSGrid(BondRestr)
                Bonds.SetTable(bondTable, True)
                Bonds.AutoSizeColumns(False)
                for r in range(len(bondList)):
                    for c in [0,1,4]:
                        Bonds.SetReadOnly(r,c,True)
                        Bonds.SetCellStyle(r,c,VERY_LIGHT_GREY,True)
                Bonds.Bind(wg.EVT_GRID_LABEL_LEFT_CLICK,OnRowSelect)
                if 'phoenix' in wx.version():
                    Bonds.Bind(wg.EVT_GRID_CELL_CHANGED, OnCellChange)
                else:
                    Bonds.Bind(wg.EVT_GRID_CELL_CHANGE, OnCellChange)
                G2frame.Bind(wx.EVT_MENU, OnDeleteRestraint, id=G2G.wxID_RESTDELETE)
                G2frame.Bind(wx.EVT_MENU, OnChangeValue, id=G2G.wxID_RESRCHANGEVAL)
                G2frame.Bind(wx.EVT_MENU, OnChangeEsd, id=G2G.wxID_RESTCHANGEESD)
                mainSizer.Add(wx.StaticText(BondRestr,-1,
                    'Bond restraints: sum(wt*(delt/sig)^2) =    %.2f, mean(wt*(delt/sig)^2) =    %.2f'    \
                    %(chisq,chisq/len(bondList))),0,WACV)
                Bonds.SetScrollRate(10,10)
                Bonds.SetMinSize((-1,300))
                mainSizer.Add(Bonds,1,wx.EXPAND,1)
            else:
                mainSizer.Add(wx.StaticText(BondRestr,-1,'No bond distance restraints for this phase'),0,)
        else:
            mainSizer.Add(wx.StaticText(BondRestr,-1,'No bond distance restraints for this phase'),0,)

        wx.CallAfter(G2phsGUI.SetPhaseWindow,BondRestr,mainSizer,Scroll=0)
        
    def UpdateAngleRestr(angleRestData):
        
        def OnCellChange(event):
            r,c =  event.GetRow(),event.GetCol()
            try:
                new = float(angleTable.GetValue(r,c))
                if new <= 0. or new > 180.:
                    raise ValueError
                angleRestData['Angles'][r][c] = new
            except ValueError:
                pass            
            wx.CallAfter(UpdateAngleRestr,angleRestData)                
            
        def OnChangeValue(event):
            rows = GetSelectedRows(Angles)
            if not rows:
                return
            Angles.ClearSelection()
            val = angleList[rows[0]][2]
            dlg = G2G.SingleFloatDialog(G2frame,'New value','Enter new value for angle',val,[0.,360.],'%.2f')
            if dlg.ShowModal() == wx.ID_OK:
                parm = dlg.GetValue()
                for r in rows:
                    angleRestData['Angles'][r][2] = parm
            dlg.Destroy()
            UpdateAngleRestr(angleRestData)                

        def OnChangeEsd(event):
            rows = GetSelectedRows(Angles)
            if not rows:
                return
            Angles.ClearSelection()
            val = angleList[rows[0]][3]
            dlg = G2G.SingleFloatDialog(G2frame,'New value','Enter new esd for angle',val,[0.,5.],'%.2f')
            if dlg.ShowModal() == wx.ID_OK:
                parm = dlg.GetValue()
                for r in rows:
                    angleRestData['Angles'][r][3] = parm
            dlg.Destroy()
            UpdateAngleRestr(angleRestData)                
                                            
        def OnDeleteRestraint(event):
            rows = GetSelectedRows(Angles)
            G2frame.GetStatusBar().SetStatusText('',1)
            if not rows:
                G2frame.GetStatusBar().SetStatusText('First select restraints to be deleted',1)
                return
            rows.sort()
            rows.reverse()
            for row in rows:
                angleList.remove(angleList[row])
            UpdateAngleRestr(angleRestData)                
            
        if AngleRestr.GetSizer():
            AngleRestr.GetSizer().Clear(True)
        mainSizer = wx.BoxSizer(wx.VERTICAL)
        mainSizer.Add((5,5),0)
        mainSizer.Add(WtBox(AngleRestr,angleRestData),0,WACV)

        angleList = angleRestData['Angles']
        if len(angleList):
            table = []
            rowLabels = []
            bad = []
            chisq = 0.
            Types = [wg.GRID_VALUE_STRING,]+4*[wg.GRID_VALUE_FLOAT+':10,2',]
            if 'macro' in General['Type']:
                colLabels = ['(res) A - (res) B - (res) C','calc','target','esd','delt/sig']
                for i,[indx,ops,obs,esd] in enumerate(angleList):
                    try:
                        atoms = G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,0,4)
                        name = ''
                        for atom in atoms:
                            name += '('+atom[1]+atom[0].strip()+atom[2]+') '+atom[3]+' - '
                        XYZ = np.array(G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,cx,3))
                        calc = G2mth.getRestAngle(XYZ,Amat)
                        chisq += angleRestData['wtFactor']*((obs-calc)/esd)**2
                        table.append([name[:-3],calc,obs,esd,(obs-calc)/esd])
                        rowLabels.append(str(i))                                
                    except KeyError:
                        print ('**** WARNING - missing atom - restraint deleted ****')
                        bad.append(i)
            else:
                colLabels = ['A+SymOp - B+SymOp - C+SymOp','calc','target','esd','delt/sig']
                for i,[indx,ops,obs,esd] in enumerate(angleList):
                    try:
                        atoms = G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,ct-1)
                        name = atoms[0]+'+('+ops[0]+') - '+atoms[1]+'+('+ops[1]+') - '+atoms[2]+ \
                        '+('+ops[2]+')'
                        XYZ = np.array(G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,cx,3))
                        XYZ = G2mth.getSyXYZ(XYZ,ops,SGData)
                        calc = G2mth.getRestAngle(XYZ,Amat)
                        chisq += angleRestData['wtFactor']*((obs-calc)/esd)**2
                        table.append([name,calc,obs,esd,(obs-calc)/esd])
                        rowLabels.append(str(i))
                    except KeyError:
                        print ('**** WARNING - missing atom - restraint deleted ****')
                        bad.append(i)
            if len(bad):
                bad.reverse()
                for ibad in bad:
                    del angleList[ibad]
            if len(angleList):
                angleTable = G2G.Table(table,rowLabels=rowLabels,colLabels=colLabels,types=Types)
                Angles = G2G.GSGrid(AngleRestr)
                Angles.SetTable(angleTable, True)
                Angles.AutoSizeColumns(False)
                for r in range(len(angleList)):
                    for c in [0,1,4]:
                        Angles.SetReadOnly(r,c,True)
                        Angles.SetCellStyle(r,c,VERY_LIGHT_GREY,True)
                Angles.Bind(wg.EVT_GRID_LABEL_LEFT_CLICK,OnRowSelect)
                if 'phoenix' in wx.version():
                    Angles.Bind(wg.EVT_GRID_CELL_CHANGED, OnCellChange)
                else:
                    Angles.Bind(wg.EVT_GRID_CELL_CHANGE, OnCellChange)
                G2frame.Bind(wx.EVT_MENU, OnDeleteRestraint, id=G2G.wxID_RESTDELETE)
                G2frame.Bind(wx.EVT_MENU, OnChangeValue, id=G2G.wxID_RESRCHANGEVAL)
                G2frame.Bind(wx.EVT_MENU, OnChangeEsd, id=G2G.wxID_RESTCHANGEESD)
                mainSizer.Add(wx.StaticText(AngleRestr,-1,
                    'Angle restraints: sum(wt*(delt/sig)^2) =    %.2f, mean(wt*(delt/sig)^2) =    %.2f'    \
                    %(chisq,chisq/len(angleList))),0,WACV)
                Angles.SetScrollRate(10,10)
                Angles.SetMinSize((-1,300))
                mainSizer.Add(Angles,1,wx.EXPAND,1)
            else:
                mainSizer.Add(wx.StaticText(AngleRestr,-1,'No bond angle restraints for this phase'),0,)
        else:
            mainSizer.Add(wx.StaticText(AngleRestr,-1,'No bond angle restraints for this phase'),0,)
        wx.CallAfter(G2phsGUI.SetPhaseWindow,AngleRestr,mainSizer,Scroll=0)
    
    def UpdatePlaneRestr(planeRestData):
        
        items = G2frame.dataWindow.RestraintEdit.GetMenuItems()
        for item in items:
            if item.GetLabel() in ['Change value']:
                item.Enable(False)

        def OnCellChange(event):
            r,c =  event.GetRow(),event.GetCol()
            try:
                new = float(planeTable.GetValue(r,c))
                if new <= 0.:
                    raise ValueError
                planeRestData['Planes'][r][c] = new
            except ValueError:
                pass            
            wx.CallAfter(UpdatePlaneRestr,planeRestData)                
            
        def OnChangeEsd(event):
            rows = GetSelectedRows(Planes)
            if not rows:
                return
            Planes.ClearSelection()
            val = planeList[rows[0]][3]
            dlg = G2G.SingleFloatDialog(G2frame,'New value','Enter new esd for plane',val,[0.,5.],'%.2f')
            if dlg.ShowModal() == wx.ID_OK:
                parm = dlg.GetValue()
                for r in rows:
                    planeRestData['Planes'][r][3] = parm
            dlg.Destroy()
            UpdatePlaneRestr(planeRestData)                
                                            
        def OnDeleteRestraint(event):
            rows = GetSelectedRows(Planes)
            G2frame.GetStatusBar().SetStatusText('',1)
            if not rows:
                G2frame.GetStatusBar().SetStatusText('First select restraints to be deleted',1)
                return
            rows.sort()
            rows.reverse()
            for row in rows:
                planeList.remove(planeList[row])
            UpdatePlaneRestr(planeRestData)                
            
        if PlaneRestr.GetSizer():
            PlaneRestr.GetSizer().Clear(True)
        mainSizer = wx.BoxSizer(wx.VERTICAL)
        mainSizer.Add((5,5),0)
        mainSizer.Add(WtBox(PlaneRestr,planeRestData),0,WACV)

        planeList = planeRestData['Planes']
        if len(planeList):
            table = []
            rowLabels = []
            bad = []
            chisq = 0.
            Types = [wg.GRID_VALUE_STRING,]+3*[wg.GRID_VALUE_FLOAT+':10,2',]
            if 'macro' in General['Type']:
                colLabels = ['(res) atom','calc','target','esd']
                for i,[indx,ops,obs,esd] in enumerate(planeList):
                    try:
                        atoms = G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,0,4)
                        name = ''
                        for a,atom in enumerate(atoms):
                            name += '('+atom[1]+atom[0].strip()+atom[2]+') '+atom[3]+' - '
                            if (a+1)%3 == 0:
                                name += '\n'
                        XYZ = np.array(G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,cx,3))
                        calc = G2mth.getRestPlane(XYZ,Amat)
                        chisq += planeRestData['wtFactor']*((calc)/esd)**2
                        table.append([name[:-3],calc,obs,esd])
                        rowLabels.append(str(i))
                    except KeyError:
                        print ('**** WARNING - missing atom - restraint deleted ****')
                        bad.append(i)
            else:                                
                colLabels = ['atom+SymOp','calc','target','esd']
                for i,[indx,ops,obs,esd] in enumerate(planeList):
                    try:
                        atoms = G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,ct-1)
                        XYZ = np.array(G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,cx,3))
                        XYZ = G2mth.getSyXYZ(XYZ,ops,SGData)
                        calc = G2mth.getRestPlane(XYZ,Amat)
                        chisq += planeRestData['wtFactor']*((calc)/esd)**2
                        name = ''
                        for a,atom in enumerate(atoms):
                            name += atom+'+ ('+ops[a]+'),'
                            if (a+1)%3 == 0:
                                name += '\n'
                        table.append([name[:-1],calc,obs,esd])
                        rowLabels.append(str(i))
                    except KeyError:
                        print ('**** WARNING - missing atom - restraint deleted ****')
                        bad.append(i)
            if len(bad):
                bad.reverse()
                for ibad in bad:
                    del planeList[ibad]
            if len(planeList):
                planeTable = G2G.Table(table,rowLabels=rowLabels,colLabels=colLabels,types=Types)
                Planes = G2G.GSGrid(PlaneRestr)
                Planes.SetTable(planeTable, True)
                Planes.AutoSizeColumns(False)
                Planes.AutoSizeRows(False)
                for r in range(len(planeList)):
                    for c in range(3):
                        Planes.SetReadOnly(r,c,True)
                        Planes.SetCellStyle(r,c,VERY_LIGHT_GREY,True)
                Planes.Bind(wg.EVT_GRID_LABEL_LEFT_CLICK,OnRowSelect)
                if 'phoenix' in wx.version():
                    Planes.Bind(wg.EVT_GRID_CELL_CHANGED, OnCellChange)
                else:
                    Planes.Bind(wg.EVT_GRID_CELL_CHANGE, OnCellChange)
                G2frame.Bind(wx.EVT_MENU, OnDeleteRestraint, id=G2G.wxID_RESTDELETE)
                G2frame.Bind(wx.EVT_MENU, OnChangeEsd, id=G2G.wxID_RESTCHANGEESD)
                mainSizer.Add(wx.StaticText(PlaneRestr,-1,
                    'Plane restraints: sum(wt*(delt/sig)^2) =    %.2f, mean(wt*(delt/sig)^2) =    %.2f'    \
                    %(chisq,chisq/len(planeList))),0,WACV)
                Planes.SetScrollRate(10,10)
                Planes.SetMinSize((-1,300))
                mainSizer.Add(Planes,1,wx.EXPAND,1)
            else:
                mainSizer.Add(wx.StaticText(PlaneRestr,-1,'No plane restraints for this phase'),0,)
        else:
            mainSizer.Add(wx.StaticText(PlaneRestr,-1,'No plane restraints for this phase'),0,)

        G2phsGUI.SetPhaseWindow(PlaneRestr,mainSizer,Scroll=0)
    
    def UpdateChiralRestr(chiralRestData):

        def OnCellChange(event):
            r,c =  event.GetRow(),event.GetCol()
            try:
                new = float(volumeTable.GetValue(r,c))
                if new <= 0.:
                    raise ValueError
                chiralRestData['Volumes'][r][c] = new
            except ValueError:
                pass            
            wx.CallAfter(UpdateChiralRestr,chiralRestData)                
            
        def OnDeleteRestraint(event):
            rows = GetSelectedRows(Volumes)
            G2frame.GetStatusBar().SetStatusText('',1)
            if not rows:
                G2frame.GetStatusBar().SetStatusText('First select restraints to be deleted',1)
                return
            rows.sort()
            rows.reverse()
            for row in rows:
                volumeList.remove(volumeList[row])
            UpdateChiralRestr(chiralRestData)                
            
        def OnChangeValue(event):
            rows = GetSelectedRows(Volumes)
            if not rows:
                return
            Volumes.ClearSelection()
            val = volumeList[rows[0]][2]
            dlg = G2G.SingleFloatDialog(G2frame,'New value','Enter new value for chiral volume',val,[0.,360.],'%.2f')
            if dlg.ShowModal() == wx.ID_OK:
                parm = dlg.GetValue()
                for r in rows:
                    chiralRestData['Volumes'][r][2] = parm
            dlg.Destroy()
            UpdateChiralRestr(chiralRestData)                

        def OnChangeEsd(event):
            rows = GetSelectedRows(Volumes)
            if not rows:
                return
            Volumes.ClearSelection()
            val = volumeList[rows[0]][3]
            dlg = G2G.SingleFloatDialog(G2frame,'New value','Enter new esd for chiral volume',val,[0.,5.],'%.2f')
            if dlg.ShowModal() == wx.ID_OK:
                parm = dlg.GetValue()
                for r in rows:
                    chiralRestData['Volumes'][r][3] = parm
            dlg.Destroy()
            UpdateChiralRestr(chiralRestData)                
                                            
        if ChiralRestr.GetSizer(): ChiralRestr.GetSizer().Clear(True)
        mainSizer = wx.BoxSizer(wx.VERTICAL)
        mainSizer.Add((5,5),0)
        mainSizer.Add(WtBox(ChiralRestr,chiralRestData),0,WACV)

        volumeList = chiralRestData['Volumes']
        if len(volumeList):
            table = []
            rowLabels = []
            bad = []
            chisq = 0.
            Types = [wg.GRID_VALUE_STRING,]+4*[wg.GRID_VALUE_FLOAT+':10,2',]
            if 'macro' in General['Type']:
                colLabels = ['(res) O (res) A (res) B (res) C','calc','target','esd','delt/sig']
                for i,[indx,ops,obs,esd] in enumerate(volumeList):
                    try:
                        atoms = G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,0,4)
                        name = ''
                        for atom in atoms:
                            name += '('+atom[1]+atom[0].strip()+atom[2]+') '+atom[3]+' '
                        XYZ = np.array(G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,cx,3))
                        calc = G2mth.getRestChiral(XYZ,Amat)
                        chisq += chiralRestData['wtFactor']*((obs-calc)/esd)**2
                        table.append([name,calc,obs,esd,(obs-calc)/esd])
                        rowLabels.append(str(i))
                    except KeyError:
                        print ('**** WARNING - missing atom - restraint deleted ****')
                        bad.append(i)
            else:
                colLabels = ['O+SymOp  A+SymOp  B+SymOp  C+SymOp)','calc','target','esd','delt/sig']
                for i,[indx,ops,obs,esd] in enumerate(volumeList):
                    try:
                        atoms = G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,ct-1)
                        name = atoms[0]+'+('+ops[0]+') '+atoms[1]+'+('+ops[1]+') '+atoms[2]+ \
                            '+('+ops[2]+') '+atoms[3]+'+('+ops[3]+')'
                        XYZ = np.array(G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,cx,3))
                        XYZ = G2mth.getSyXYZ(XYZ,ops,SGData)
                        calc = G2mth.getRestChiral(XYZ,Amat)
                        chisq += chiralRestData['wtFactor']*((obs-calc)/esd)**2
                        table.append([name,calc,obs,esd,(obs-calc)/esd])
                        rowLabels.append(str(i))
                    except KeyError:
                        print ('**** WARNING - missing atom - restraint deleted ****')
                        bad.append(i)
            if len(bad):
                bad.reverse()
                for ibad in bad:
                    del volumeList[ibad]
            if len(volumeList):
                volumeTable = G2G.Table(table,rowLabels=rowLabels,colLabels=colLabels,types=Types)
                Volumes = G2G.GSGrid(ChiralRestr)
                Volumes.SetTable(volumeTable, True)
                Volumes.AutoSizeColumns(False)
                for r in range(len(volumeList)):
                    for c in [0,1,4]:
                        Volumes.SetReadOnly(r,c,True)
                        Volumes.SetCellStyle(r,c,VERY_LIGHT_GREY,True)
                Volumes.Bind(wg.EVT_GRID_LABEL_LEFT_CLICK,OnRowSelect)
                if 'phoenix' in wx.version():
                    Volumes.Bind(wg.EVT_GRID_CELL_CHANGED, OnCellChange)
                else:
                    Volumes.Bind(wg.EVT_GRID_CELL_CHANGE, OnCellChange)
                G2frame.Bind(wx.EVT_MENU, OnChangeValue, id=G2G.wxID_RESRCHANGEVAL)
                G2frame.Bind(wx.EVT_MENU, OnChangeEsd, id=G2G.wxID_RESTCHANGEESD)
                mainSizer.Add(wx.StaticText(ChiralRestr,-1,
                    'Chiral volume restraints: sum(wt*(delt/sig)^2) =    %.2f, mean(wt*(delt/sig)^2) =    %.2f'    \
                    %(chisq,chisq/len(volumeList))),0,WACV)
                Volumes.SetScrollRate(10,10)
                Volumes.SetMinSize((-1,300))
                mainSizer.Add(Volumes,1,wx.EXPAND,1)
            else:
                mainSizer.Add(wx.StaticText(ChiralRestr,-1,'No chiral volume restraints for this phase'),0,)
        else:
            mainSizer.Add(wx.StaticText(ChiralRestr,-1,'No chiral volume restraints for this phase'),0,)

        G2phsGUI.SetPhaseWindow(ChiralRestr,mainSizer,Scroll=0)
    
    def UpdateTorsionRestr(torsionRestData):

        def OnCellChange(event):
            r,c =  event.GetRow(),event.GetCol()
            try:
                new = float(torsionTable.GetValue(r,c))
                if new <= 0. or new > 5.:
                    raise ValueError
                torsionRestData['Torsions'][r][3] = new     #only esd is editable
            except ValueError:
                pass            
            wx.CallAfter(UpdateTorsionRestr,torsionRestData)                
            
        def OnDeleteRestraint(event):
            rows = GetSelectedRows(TorsionRestr.Torsions)
            G2frame.GetStatusBar().SetStatusText('',1)
            if not rows:
                return
                G2frame.GetStatusBar().SetStatusText('First select restraints to be deleted',1)
            rows.sort()
            rows.reverse()
            for row in rows:
                torsionList.remove(torsionList[row])
            wx.CallAfter(UpdateTorsionRestr,torsionRestData)                
            
        def OnChangeEsd(event):
            rows = GetSelectedRows(TorsionRestr.Torsions)
            if not rows:
                return
            TorsionRestr.Torsions.ClearSelection()
            val = torsionList[rows[0]][4]
            dlg = G2G.SingleFloatDialog(G2frame,'New value','Enter new esd for torsion restraints',val,[0.,5.],'%.2f')
            if dlg.ShowModal() == wx.ID_OK:
                parm = dlg.GetValue()
                for r in rows:
                    torsionRestData['Torsions'][r][4] = parm
            dlg.Destroy()
            wx.CallAfter(UpdateTorsionRestr,torsionRestData) 

        def coeffSizer():               
            table = []
            rowLabels = []
            Types = 9*[wg.GRID_VALUE_FLOAT+':10,4',]
            colLabels = ['Mag A','Pos A','Width A','Mag B','Pos B','Width B','Mag C','Pos C','Width C']
            for item in coeffDict:
                rowLabels.append(item)
                table.append(coeffDict[item])
            coeffTable = G2G.Table(table,rowLabels=rowLabels,colLabels=colLabels,types=Types)
            Coeff = G2G.GSGrid(TorsionRestr)
            Coeff.SetScrollRate(0,10)
            Coeff.SetTable(coeffTable, True)
            Coeff.AutoSizeColumns(False)
            for r in range(len(coeffDict)):
                for c in range(9):
                    Coeff.SetReadOnly(r,c,True)
                    Coeff.SetCellStyle(r,c,VERY_LIGHT_GREY,True)
            Coeff.SetMaxSize((-1,200))
            return Coeff
                                            
        if TorsionRestr.GetSizer(): TorsionRestr.GetSizer().Clear(True)
        mainSizer = wx.BoxSizer(wx.VERTICAL)
        mainSizer.Add((5,5),0)
        mainSizer.Add(WtBox(TorsionRestr,torsionRestData),0,WACV)
        
        coeffDict = torsionRestData['Coeff']
        torsionList = torsionRestData['Torsions']
        if len(coeffDict):
            mainSizer.Add((5,5))
            mainSizer.Add(wx.StaticText(TorsionRestr,-1,'Torsion function coefficients:'),0,WACV)
            mainSizer.Add(coeffSizer(),1,wx.EXPAND,1)        
        
        if len(torsionList):
            mainSizer.Add(wx.StaticText(TorsionRestr,-1,'Torsion restraints:'),0,WACV)
            table = []
            rowLabels = []
            bad = []
            chisq = 0.
            Types = 2*[wg.GRID_VALUE_STRING,]+4*[wg.GRID_VALUE_FLOAT+':10,2',]
            if 'macro' in General['Type']:
                colLabels = ['(res) A  B  C  D','coef name','torsion','obs E','restr','esd']
                for i,[indx,ops,cofName,esd] in enumerate(torsionList):
                    try:
                        atoms = G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,0,4)
                        name = '('+atoms[2][1]+atoms[2][0].strip()+atoms[2][2]+')'
                        for atom in atoms:
                            name += '  '+atom[3]
                        XYZ = np.array(G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,cx,3))
                        tor = G2mth.getRestTorsion(XYZ,Amat)
                        restr,calc = G2mth.calcTorsionEnergy(tor,coeffDict[cofName])
                        chisq += torsionRestData['wtFactor']*(restr/esd)**2
                        table.append([name,cofName,tor,calc,restr,esd])
                        rowLabels.append(str(i))
                    except KeyError:
                        print ('**** WARNING - missing atom - restraint deleted ****')
                        bad.append(i)
            if len(bad):
                bad.reverse()
                for ibad in bad:
                    del torsionList[ibad]
            torsionTable = G2G.Table(table,rowLabels=rowLabels,colLabels=colLabels,types=Types)
            TorsionRestr.Torsions = G2G.GSGrid(TorsionRestr)
            TorsionRestr.Torsions.SetTable(torsionTable, True)
            TorsionRestr.Torsions.AutoSizeColumns(False)
            for r in range(len(torsionList)):
                for c in range(5):
                    TorsionRestr.Torsions.SetReadOnly(r,c,True)
                    TorsionRestr.Torsions.SetCellStyle(r,c,VERY_LIGHT_GREY,True)
            TorsionRestr.Torsions.Bind(wg.EVT_GRID_LABEL_LEFT_CLICK,OnRowSelect)
            if 'phoenix' in wx.version():
                TorsionRestr.Torsions.Bind(wg.EVT_GRID_CELL_CHANGED, OnCellChange)
            else:
                TorsionRestr.Torsions.Bind(wg.EVT_GRID_CELL_CHANGE, OnCellChange)
            G2frame.Bind(wx.EVT_MENU, OnDeleteRestraint, id=G2G.wxID_RESTDELETE)
            G2frame.Bind(wx.EVT_MENU, OnChangeEsd, id=G2G.wxID_RESTCHANGEESD)
            mainSizer.Add(wx.StaticText(TorsionRestr,-1,
                'Torsion restraints: sum(wt*(delt/sig)^2) =    %.2f, mean(wt*(delt/sig)^2) =    %.2f'    \
                %(chisq,chisq/len(torsionList))),0,WACV)
            TorsionRestr.Torsions.SetScrollRate(10,10)
            TorsionRestr.Torsions.SetMinSize((-1,300))
            mainSizer.Add(TorsionRestr.Torsions,1,wx.EXPAND,1)
                
        else:
            mainSizer.Add(wx.StaticText(TorsionRestr,-1,'No torsion restraints for this phase'),0,)

        G2phsGUI.SetPhaseWindow(TorsionRestr,mainSizer,Scroll=0)

    def UpdateRamaRestr(ramaRestData):

        def OnCellChange(event):
            r,c =  event.GetRow(),event.GetCol()
            try:
                new = float(ramaTable.GetValue(r,c))
                if new <= 0. or new > 5.:
                    raise ValueError
                ramaRestData['Ramas'][r][4] = new     #only esd is editable
            except ValueError:
                pass            
            wx.CallAfter(UpdateRamaRestr,ramaRestData)                
            
        def OnDeleteRestraint(event):
            rows = GetSelectedRows(RamaRestr.Ramas)
            G2frame.GetStatusBar().SetStatusText('',1)
            if not rows:
                G2frame.GetStatusBar().SetStatusText('First select restraints to be deleted',1)
                return
            rows.sort()
            rows.reverse()
            for row in rows:
                ramaList.remove(ramaList[row])
            UpdateRamaRestr(ramaRestData)                
            
        def OnChangeEsd(event):
            rows = GetSelectedRows(RamaRestr.Ramas)
            if not rows:
                return
            RamaRestr.Ramas.ClearSelection()
            val = ramaList[rows[0]][4]
            dlg = G2G.SingleFloatDialog(G2frame,'New value','Enter new esd for energy',val,[0.,5.],'%.2f')
            if dlg.ShowModal() == wx.ID_OK:
                parm = dlg.GetValue()
                for r in rows:
                    ramaRestData['Ramas'][r][4] = parm
            dlg.Destroy()
            UpdateRamaRestr(ramaRestData)

        def coeffSizer():
            table = []
            rowLabels = []
            Types = 6*[wg.GRID_VALUE_FLOAT+':10,4',]
            colLabels = ['Mag','Pos phi','Pos psi','sig(phi)','sig(psi)','sig(cov)']
            for item in coeffDict:
                for i,term in enumerate(coeffDict[item]):
                    rowLabels.append(item+' term:'+str(i))
                    table.append(term)
            coeffTable = G2G.Table(table,rowLabels=rowLabels,colLabels=colLabels,types=Types)
            Coeff = G2G.GSGrid(RamaRestr)
            Coeff.SetScrollRate(0,10)
            Coeff.SetTable(coeffTable, True)
            Coeff.AutoSizeColumns(False)
            for r in range(Coeff.GetNumberRows()):
                for c in range(6):
                    Coeff.SetReadOnly(r,c,True)
                    Coeff.SetCellStyle(r,c,VERY_LIGHT_GREY,True)
            Coeff.SetMaxSize((-1,200))
            return Coeff
                                                    
        if RamaRestr.GetSizer(): RamaRestr.GetSizer().Clear(True)
        mainSizer = wx.BoxSizer(wx.VERTICAL)
        mainSizer.Add((5,5),0)
        mainSizer.Add(WtBox(RamaRestr,ramaRestData),0,WACV)
        ramaList = ramaRestData['Ramas']
        coeffDict = ramaRestData['Coeff']
        if len(coeffDict):
            mainSizer.Add(wx.StaticText(RamaRestr,-1,'Ramachandran function coefficients:'),0,WACV)
            mainSizer.Add(coeffSizer(),1,wx.EXPAND,1)
            
        if len(ramaList):
            mainSizer.Add(wx.StaticText(RamaRestr,-1,'Ramachandran restraints:'),0,WACV)
            table = []
            rowLabels = []
            bad = []
            chisq = 0.
            Types = 2*[wg.GRID_VALUE_STRING,]+5*[wg.GRID_VALUE_FLOAT+':10,2',]
            if 'macro' in General['Type']:
                colLabels = ['(res) A  B  C  D  E','coef name','phi','psi','obs E','restr','esd']
                for i,[indx,ops,cofName,esd] in enumerate(ramaList):
                    try:
                        atoms = G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,0,4)
                        name = '('+atoms[3][1]+atoms[3][0].strip()+atoms[3][2]+')'
                        for atom in atoms:
                            name += '  '+atom[3]
                        XYZ = np.array(G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,cx,3))
                        phi,psi = G2mth.getRestRama(XYZ,Amat)
                        restr,calc = G2mth.calcRamaEnergy(phi,psi,coeffDict[cofName])
                        chisq += ramaRestData['wtFactor']*(restr/esd)**2
                        table.append([name,cofName,phi,psi,calc,restr,esd])
                        rowLabels.append(str(i))
                    except KeyError:
                        print ('**** WARNING - missing atom - restraint deleted ****')
                        bad.append(i)
            if len(bad):
                bad.reverse()
                for ibad in bad:
                    del ramaList[ibad]
            if len(ramaList):
                ramaTable = G2G.Table(table,rowLabels=rowLabels,colLabels=colLabels,types=Types)
                RamaRestr.Ramas = G2G.GSGrid(RamaRestr)
                RamaRestr.Ramas.SetTable(ramaTable, True)
                RamaRestr.Ramas.AutoSizeColumns(False)
                for r in range(len(ramaList)):
                    for c in range(6):
                        RamaRestr.Ramas.SetReadOnly(r,c,True)
                        RamaRestr.Ramas.SetCellStyle(r,c,VERY_LIGHT_GREY,True)
                RamaRestr.Ramas.Bind(wg.EVT_GRID_LABEL_LEFT_CLICK,OnRowSelect)
                if 'phoenix' in wx.version():
                    RamaRestr.Ramas.Bind(wg.EVT_GRID_CELL_CHANGED, OnCellChange)
                else:
                    RamaRestr.Ramas.Bind(wg.EVT_GRID_CELL_CHANGE, OnCellChange)
                G2frame.Bind(wx.EVT_MENU, OnDeleteRestraint, id=G2G.wxID_RESTDELETE)
                G2frame.Bind(wx.EVT_MENU, OnChangeEsd, id=G2G.wxID_RESTCHANGEESD)
                mainSizer.Add(wx.StaticText(RamaRestr,-1,
                    'Ramachandran restraints: sum(wt*(delt/sig)^2) =    %.2f, mean(wt*(delt/sig)^2) =    %.2f'    \
                    %(chisq,chisq/len(ramaList))),0,WACV)
                RamaRestr.Ramas.SetScrollRate(10,10)
                RamaRestr.Ramas.SetMinSize((-1,300))
                mainSizer.Add(RamaRestr.Ramas,1,wx.EXPAND,1)
        else:
            mainSizer.Add(wx.StaticText(RamaRestr,-1,'No Ramachandran restraints for this phase'),0,)

        G2phsGUI.SetPhaseWindow(RamaRestr,mainSizer,Scroll=0)

    def UpdateChemcompRestr(chemcompRestData):
        
        def OnCellChange(event):
            r,c =  event.GetRow(),event.GetCol()
            rowLabl = ChemComps.GetRowLabelValue(r)
            row = int(rowLabl.split(':')[1])
            if 'Restr' in rowLabl:
                try:
                    new = float(chemcompTable.GetValue(r,c))
                    chemcompRestData['Sites'][row][c-2] = new         #obsd or esd
                except ValueError:
                    pass
            else:
                try:
                    new = float(chemcompTable.GetValue(r,c))
                    id = int(rowLabl.split(':')[2])
                    chemcompRestData['Sites'][row][1][id] = new     #only factor
                except ValueError:
                    pass
            wx.CallAfter(UpdateChemcompRestr,chemcompRestData)                
            
        def OnDeleteRestraint(event):
            #rows = GetSelectedRows()
            rows = ChemComps.GetSelectedRows()
            G2frame.GetStatusBar().SetStatusText('',1)
            if not rows:
                G2frame.GetStatusBar().SetStatusText('First select restraints to be deleted',1)
                return
            rowLabl = ChemComps.GetRowLabelValue(r)
            row = int(rowLabl.split(':')[1])
            if 'Restr' in rowLabl:
                del chemcompList[row]
            else:
                term = int(rowLabl.split(':')[2])
                del chemcompList[row][0][term]
                del chemcompList[row][1][term]
            UpdateChemcompRestr(chemcompRestData)                
            
        def OnChangeValue(event):
            rows = GetSelectedRows(ChemComps)
            if not rows:
                return
            ChemComps.ClearSelection()
            dlg = G2G.SingleFloatDialog(G2frame,'New value',
                'Enter new value for restraint multiplier',1.0,[-1.e6,1.e6],'%.2f')
            if dlg.ShowModal() == wx.ID_OK:
                parm = dlg.GetValue()
                for r in rows:
                    rowLabl = ChemComps.GetRowLabelValue(r)
                    if 'term' in rowLabl:
                        items = rowLabl.split(':')
                        chemcompRestData['Sites'][int(items[1])][1][int(items[2])] = parm
            dlg.Destroy()
            UpdateChemcompRestr(chemcompRestData)                

        if ChemCompRestr.GetSizer(): ChemCompRestr.GetSizer().Clear(True)
        mainSizer = wx.BoxSizer(wx.VERTICAL)
        mainSizer.Add((5,5),0)
        mainSizer.Add(WtBox(ChemCompRestr,chemcompRestData),0,WACV)
        mainSizer.Add(wx.StaticText(ChemCompRestr,-1, 
            'NB: The chemical restraint sum is over the unit cell contents'),0,WACV)
        mainSizer.Add((5,5),0)

        chemcompList = chemcompRestData['Sites']
        if len(chemcompList):
            table = []
            rowLabels = []
            bad = []
            chisq = 0.
            Types = [wg.GRID_VALUE_STRING,]+5*[wg.GRID_VALUE_FLOAT+':10,2',]
            colLabels = ['Atoms','mul*frac','factor','calc','target','esd']
            for i,[indx,factors,obs,esd] in enumerate(chemcompList):
                try:
                    atoms = G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,ct-1)
                    mul = np.array(G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,cs+1))
                    frac = np.array(G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,cs-1))
                    mulfrac = mul*frac
                    calcs = mul*frac*factors
                    chisq += chemcompRestData['wtFactor']*((obs-np.sum(calcs))/esd)**2
                    for iatm,[atom,mf,fr,clc] in enumerate(zip(atoms,mulfrac,factors,calcs)):
                        table.append([atom,mf,fr,clc,'',''])
                        rowLabels.append('term:'+str(i)+':'+str(iatm))
                    table.append(['Sum','','',np.sum(calcs),obs,esd])
                    rowLabels.append('Restr:'+str(i))
                except KeyError:
                    print ('**** WARNING - missing atom - restraint deleted ****')
                    bad.append(i)
            if len(bad):
                bad.reverse()
                for ibad in bad:
                    del chemcompList[ibad]
            if len(chemcompList):
                chemcompTable = G2G.Table(table,rowLabels=rowLabels,colLabels=colLabels,types=Types)
                ChemComps = G2G.GSGrid(ChemCompRestr)
                ChemComps.SetTable(chemcompTable, True)
                ChemComps.AutoSizeColumns(False)
                for r in range(chemcompTable.GetNumberRows()):
                    for c in range(2):
                        ChemComps.SetReadOnly(r,c,True)
                        ChemComps.SetCellStyle(r,c,VERY_LIGHT_GREY,True)
                    if 'Restr' in ChemComps.GetRowLabelValue(r):
                        for c in range(4):
                            ChemComps.SetReadOnly(r,c,True)
                            ChemComps.SetCellStyle(r,c,VERY_LIGHT_GREY,True)
                        for c in [1,2]:
                            ChemComps.SetCellTextColour(r,c,VERY_LIGHT_GREY)
                    else:
                        for c in [3,4,5]:
                            ChemComps.SetReadOnly(r,c,True)
                            ChemComps.SetCellStyle(r,c,VERY_LIGHT_GREY,True)
                        for c in [4,5]:
                            ChemComps.SetCellTextColour(r,c,VERY_LIGHT_GREY)
                            
                ChemComps.Bind(wg.EVT_GRID_LABEL_LEFT_CLICK,OnRowSelect)
                if 'phoenix' in wx.version():
                    ChemComps.Bind(wg.EVT_GRID_CELL_CHANGED, OnCellChange)
                else:
                    ChemComps.Bind(wg.EVT_GRID_CELL_CHANGE, OnCellChange)
                G2frame.Bind(wx.EVT_MENU, OnDeleteRestraint, id=G2G.wxID_RESTDELETE)
                G2frame.Bind(wx.EVT_MENU, OnChangeValue, id=G2G.wxID_RESRCHANGEVAL)
                mainSizer.Add(wx.StaticText(ChemCompRestr,-1,
                    'Chemical composition restraints: sum(wt*(delt/sig)^2) =    %.2f, mean(wt*(delt/sig)^2) =    %.2f'    \
                    %(chisq,chisq/len(chemcompList))),0,WACV)
                mainSizer.Add(ChemComps,0,)
            else:
                mainSizer.Add(wx.StaticText(ChemCompRestr,-1,'No chemical composition restraints for this phase'),0,)
        else:
            mainSizer.Add(wx.StaticText(ChemCompRestr,-1,'No chemical composition restraints for this phase'),0,)

        G2phsGUI.SetPhaseWindow(ChemCompRestr,mainSizer,Scroll=0)
            
    def UpdateTextureRestr(textureRestData):
            
        def OnDeleteRestraint(event):
            rows = GetSelectedRows(Textures)
            G2frame.GetStatusBar().SetStatusText('',1)
            if not rows:
                G2frame.GetStatusBar().SetStatusText('First select restraints to be deleted',1)
                return
            rows.sort()
            rows.reverse()
            for row in rows:
                textureList.remove(textureList[row])
            wx.CallAfter(UpdateTextureRestr,textureRestData)                
            
        def OnCellChange(event):
            r,c = event.GetRow(),event.GetCol()
            try:
                if c == 1:  #grid size
                    new = int(textureTable.GetValue(r,c))
                    if new < 6 or new > 24:
                        raise ValueError
                elif c in [2,4]:   #esds
                    new = float(textureTable.GetValue(r,c))
                    if new < -1. or new > 2.:
                        raise ValueError
                else:
                    new = textureTable.GetValue(r,c)
                textureRestData['HKLs'][r][c] = new
            except ValueError:
                pass            
            wx.CallAfter(UpdateTextureRestr,textureRestData)                

        if TextureRestr.GetSizer(): TextureRestr.GetSizer().Clear(True)
        mainSizer = wx.BoxSizer(wx.VERTICAL)
        mainSizer.Add((5,5),0)
        mainSizer.Add(WtBox(TextureRestr,textureRestData),0,WACV)
        mainSizer.Add(wx.StaticText(TextureRestr,-1, 
            'NB: The texture restraints suppress negative pole figure values for the selected HKLs\n'
            '    "unit esd" gives a bias toward a flatter polefigure'),0,WACV)
        mainSizer.Add((5,5),0)

        textureList = textureRestData['HKLs']
        if len(textureList):
            table = []
            rowLabels = []
            Types = [wg.GRID_VALUE_STRING,wg.GRID_VALUE_LONG,wg.GRID_VALUE_FLOAT+':10,2',
                wg.GRID_VALUE_BOOL,wg.GRID_VALUE_FLOAT+':10,2']
            colLabels = ['HKL','grid','neg esd','use unit?','unit esd']
            for i,[hkl,grid,esd1,ifesd2,esd2] in enumerate(textureList):
                table.append(['%d %d %d'%(hkl[0],hkl[1],hkl[2]),grid,esd1,ifesd2,esd2])
                rowLabels.append(str(i))
            textureTable = G2G.Table(table,rowLabels=rowLabels,colLabels=colLabels,types=Types)
            Textures = G2G.GSGrid(TextureRestr)
            Textures.SetTable(textureTable, True)
            Textures.AutoSizeColumns(False)
            for r in range(len(textureList)):
                Textures.SetReadOnly(r,0,True)
                Textures.SetCellStyle(r,0,VERY_LIGHT_GREY,True)
                if not textureTable.GetValue(r,3):
                    Textures.SetReadOnly(r,4,True)
                    Textures.SetCellStyle(r,4,VERY_LIGHT_GREY,True)
                    Textures.SetCellTextColour(r,4,VERY_LIGHT_GREY)
            Textures.Bind(wg.EVT_GRID_LABEL_LEFT_CLICK,OnRowSelect)
            if 'phoenix' in wx.version():
                Textures.Bind(wg.EVT_GRID_CELL_CHANGED, OnCellChange)
            else:
                Textures.Bind(wg.EVT_GRID_CELL_CHANGE, OnCellChange)
            G2frame.Bind(wx.EVT_MENU, OnDeleteRestraint, id=G2G.wxID_RESTDELETE)
            mainSizer.Add(Textures,0,)
        else:
            mainSizer.Add(wx.StaticText(TextureRestr,-1,'No texture restraints for this phase'),0,)
        G2phsGUI.SetPhaseWindow(TextureRestr,mainSizer,Scroll=0)
            
    def UpdateGeneralRestr(generalRestData):
        '''Display any generalized restraint expressions'''
        
        def OnEditGenRestraint(event):
            '''Edit a restraint expression'''
            n = event.GetEventObject().index
            parmDict = SetupParmDict(G2frame)
            dlg = G2exG.ExpressionDialog(G2frame,parmDict,
                exprObj=generalRestData['General'][n][0],
                header="Edit a restraint expression",
                fit=False,wildCard=G2frame.testSeqRefineMode())
            restobj = dlg.Show(True)
            if restobj:
                generalRestData['General'][n][0] = restobj
                wx.CallAfter(UpdateGeneralRestr,restrData['General'])
                
        def OnDelGenRestraint(event):              #does this work??
            '''Delete a restraint expression'''
            n = event.GetEventObject().index
            G2frame.GetStatusBar().SetStatusText('',1)
            if not n:
                G2frame.GetStatusBar().SetStatusText('First select restraints to be deleted',1)
                return
            wx.CallAfter(UpdateGeneralRestr,restrData['General'])
            
        if GeneralRestr.GetSizer(): GeneralRestr.GetSizer().Clear(True)
        mainSizer = wx.BoxSizer(wx.VERTICAL)
        mainSizer.Add((5,5),0)
        hSizer = wx.BoxSizer(wx.HORIZONTAL)
        hSizer.Add(wx.StaticText(GeneralRestr,wx.ID_ANY,'Weight factor: '))
        hSizer.Add(G2G.ValidatedTxtCtrl(GeneralRestr,generalRestData,
            'wtFactor',nDig=(10,1),typeHint=float))
        btn = G2G.G2CheckBox(GeneralRestr,'Use?',generalRestData,'Use')
        hSizer.Add(btn)
        hSizer.Add((5,5),0)
        btn = wx.Button(GeneralRestr, wx.ID_ANY,"Add restraint")
        btn.Bind(wx.EVT_BUTTON,OnAddRestraint)
        hSizer.Add(btn,0,wx.ALIGN_CENTER|wx.EXPAND|wx.ALL)
        mainSizer.Add(hSizer,0)
        mainSizer.Add((5,5),0)
        if generalRestData['General']:
            parmDict = SetupParmDict(G2frame)
            GridSiz = wx.FlexGridSizer(0,7,10,2)
            for lbl in ('expression =','target\nvalue','current\nvalue','esd'):
                GridSiz.Add(
                    wx.StaticText(GeneralRestr,wx.ID_ANY,lbl,style=wx.CENTER),
                    0,wx.ALIGN_CENTER)
            GridSiz.Add((-1,-1))
            GridSiz.Add((-1,-1))
            GridSiz.Add(
                    wx.StaticText(GeneralRestr,wx.ID_ANY,'Variables',style=wx.CENTER),
                    0,wx.ALIGN_CENTER)
            for i,rest in enumerate(generalRestData['General']):
                eq = rest[0]
                txt = eq.expression+' ='
                if len(txt) > 50:
                    txt = txt[:47]+'... '
                GridSiz.Add(wx.StaticText(GeneralRestr,wx.ID_ANY,txt))
                GridSiz.Add(
                    G2G.ValidatedTxtCtrl(GeneralRestr,rest,1,nDig=(10,3,'g'),typeHint=float)
                    )
                # evaluate the expression
                try:
                    calcobj = G2obj.ExpressionCalcObj(rest[0])
                    calcobj.SetupCalc(parmDict)
                    txt = ' {:f} '.format(calcobj.EvalExpression())
                except:
                    txt = ' (error) '
                GridSiz.Add(wx.StaticText(GeneralRestr,wx.ID_ANY,txt))
                GridSiz.Add(
                    G2G.ValidatedTxtCtrl(GeneralRestr,rest,2,nDig=(10,3,'g'),typeHint=float)
                    )
                btn = wx.Button(GeneralRestr, wx.ID_ANY,"Edit",size=(40,-1))
                btn.index = i
                btn.Bind(wx.EVT_BUTTON,OnEditGenRestraint)
                GridSiz.Add(btn)
                btn = wx.Button(GeneralRestr, wx.ID_ANY,"Delete",size=(60,-1))
                btn.index = i
                btn.Bind(wx.EVT_BUTTON,OnDelGenRestraint)
                GridSiz.Add(btn)
                txt = ''
                for i in eq.assgnVars:
                    if txt: txt += '; '
                    txt += str(i) + '=' + str(eq.assgnVars[i])
                if len(txt) > 50:
                    txt = txt[:47]+'...'
                GridSiz.Add(wx.StaticText(GeneralRestr,wx.ID_ANY,txt))
            mainSizer.Add(GridSiz)
        G2phsGUI.SetPhaseWindow(GeneralRestr,mainSizer,Scroll=0)

    
    def OnPageChanged(event):
        page = event.GetSelection()
        #G2frame.restrBook.SetSize(G2frame.dataWindow.GetClientSize())    #TODO -almost right
        text = G2frame.restrBook.GetPageText(page)
        G2frame.dataWindow.RestraintEdit.SetLabel(G2G.wxID_RESRCHANGEVAL,'Change value')
        G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_USEMOGUL,False)
        if text == 'Bond':
            G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.RestraintMenu)
            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_RESTRAINTADD,True)
            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_RESRCHANGEVAL,True)
            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_USEMOGUL,True)
            bondRestData = restrData['Bond']
            UpdateBondRestr(bondRestData)
        elif text == 'Angle':
            G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.RestraintMenu)
            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_RESTRAINTADD,True)
            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_RESRCHANGEVAL,True)
            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_USEMOGUL,True)
            angleRestData = restrData['Angle']
            UpdateAngleRestr(angleRestData)
        elif text == 'Plane':
            G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.RestraintMenu)
            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_RESTRAINTADD,True)
            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_RESRCHANGEVAL,False)
            planeRestData = restrData['Plane']
            UpdatePlaneRestr(planeRestData)
        elif text == 'Chiral':
            G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.RestraintMenu)
            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_RESTRAINTADD,False)
            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_RESRCHANGEVAL,True)
            chiralRestData = restrData['Chiral']
            UpdateChiralRestr(chiralRestData)
        elif text == 'Torsion':
            G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.RestraintMenu)
            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_RESTRAINTADD,False)
            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_RESRCHANGEVAL,False)
            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_AARESTRAINTPLOT,True)
            torsionRestData = restrData['Torsion']
            UpdateTorsionRestr(torsionRestData)
        elif text == 'Ramachandran':
            G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.RestraintMenu)
            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_RESTRAINTADD,False)
            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_RESRCHANGEVAL,False)
            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_AARESTRAINTPLOT,True)
            ramaRestData = restrData['Rama']
            UpdateRamaRestr(ramaRestData)
            wx.CallAfter(G2plt.PlotRama,G2frame,phaseName,rama,ramaName)
        elif text == 'Chem. comp.':
            G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.RestraintMenu)
            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_RESTRAINTADD,True)
            G2frame.dataWindow.RestraintEdit.SetLabel(G2G.wxID_RESRCHANGEVAL,'Change factor')
            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_RESRCHANGEVAL,True)
            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_RESTCHANGEESD,False)
            chemcompRestData = restrData['ChemComp']
            UpdateChemcompRestr(chemcompRestData)
        elif text == 'Texture':
            G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.RestraintMenu)
            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_RESTRAINTADD,True)
            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_RESRCHANGEVAL,True)
            textureRestData = restrData['Texture']
            UpdateTextureRestr(textureRestData)
        elif text == 'General':
            G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.RestraintMenu)
            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_RESTRAINTADD,True)
            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_RESRCHANGEVAL,False)
            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_RESTCHANGEESD,False)
            UpdateGeneralRestr(restrData['General'])
        event.Skip()

#    def RaisePage(event):
#        'Respond to a "select tab" menu button'
#        # class PseudoEvent(object):
#        #     def __init__(self,page): self.page = page
#        #     def Skip(self): pass
#        #     def GetSelection(self): return self.page
#        try:
#            i = tabIndex.get(event.GetId())
#            G2frame.restrBook.SetSelection(i)
#            #OnPageChanged(PseudoEvent(i))
#        except ValueError:
#            print('Unexpected event in RaisePage')
#
    def OnSelectPage(event):
        'Called when an item is selected from the Select page menu'
        # lookup the menu item that called us and get its text
        tabname = TabSelectionIdDict.get(event.GetId())
        if not tabname:
            print ('Warning: menu item not in dict! id= %d'%event.GetId())
            return
        # find the matching tab
        for PageNum in range(G2frame.restrBook.GetPageCount()):
            if tabname == G2frame.restrBook.GetPageText(PageNum):
                G2frame.restrBook.SetSelection(PageNum)
                return
        else:
            print ("Warning: tab "+tabname+" was not found")

    # UpdateRestraints execution starts here
    try:
        phasedata = G2frame.GetPhaseData()[phaseName]
    except KeyError:        #delete unknown or previously deleted phases from Restraints
        rId = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'Restraints')
        pId = G2gd.GetGPXtreeItemId(G2frame,rId,phaseName)
        G2frame.GPXtree.Delete(pId)
        print('Unknown phase '+phaseName+' is deleted from Restraints')
        return
    restrData = data[phaseName]
    if 'Bond' not in restrData:
        restrData['Bond'] = {'wtFactor':1.0,'Range':1.1,'Bonds':[],'Use':True}
    if 'Angle' not in restrData:
        restrData['Angle'] = {'wtFactor':1.0,'Range':0.85,'Angles':[],'Use':True}
    if 'Plane' not in restrData:
        restrData['Plane'] = {'wtFactor':1.0,'Planes':[],'Use':True}
    if 'Chiral' not in restrData:
        restrData['Chiral'] = {'wtFactor':1.0,'Volumes':[],'Use':True}
    if 'Torsion' not in restrData:
        restrData['Torsion'] = {'wtFactor':1.0,'Coeff':{},'Torsions':[],'Use':True}
    if 'Rama' not in restrData:
        restrData['Rama'] = {'wtFactor':1.0,'Coeff':{},'Ramas':[],'Use':True}
    if 'Texture' not in restrData:
        restrData['Texture'] = {'wtFactor':1.0,'HKLs':[],'Use':True}
    if 'ChemComp' not in restrData:
        restrData['ChemComp'] = {'wtFactor':1.0,'Sites':[],'Use':True}
    if 'General' not in restrData:
        restrData['General'] = {'wtFactor':1.0,'General':[], 'Use':True}
    General = phasedata['General']
    Cell = General['Cell'][1:7]          #skip flag & volume    
    Amat,Bmat = G2lat.cell2AB(Cell)
    SGData = General['SGData']
    cx,ct,cs,cia = General['AtomPtrs']
    Atoms = phasedata['Atoms']
    AtLookUp = G2mth.FillAtomLookUp(Atoms,cia+8)
    if 'macro' in General['Type']:
        Names = [atom[0]+':'+atom[1]+atom[2]+' '+atom[3].ljust(4) for atom in Atoms]
        Ids = []
        Coords = []
        Types = []
    else:    
        Names = ['all '+ name for name in General['AtomTypes']]
        iBeg = len(Names)
        Types = [name for name in General['AtomTypes']]
        Coords = [ [] for type in Types]
        Ids = [ 0 for type in Types]
        Names += [atom[ct-1] for atom in Atoms]
    Types += [atom[ct] for atom in Atoms]
    Coords += [atom[cx:cx+3] for atom in Atoms]
    Ids += [atom[cia+8] for atom in Atoms]
    rama = G2data.ramachandranDist['All']
    ramaName = 'All'
    G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.RestraintMenu)    
    G2frame.Bind(wx.EVT_MENU, OnAddRestraint, id=G2G.wxID_RESTRAINTADD)
    G2frame.Bind(wx.EVT_MENU, OnUseMogul, id=G2G.wxID_USEMOGUL)
    G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_USEMOGUL,True)
    if 'macro' in phasedata['General']['Type']:
        G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_AARESTRAINTADD,True)
        G2frame.Bind(wx.EVT_MENU, OnAddAARestraint, id=G2G.wxID_AARESTRAINTADD)
        G2frame.Bind(wx.EVT_MENU, OnPlotAARestraint, id=G2G.wxID_AARESTRAINTPLOT)
    
    # GUI defined here
    G2frame.restrBook = G2G.GSNoteBook(parent=G2frame.dataWindow)
    G2frame.dataWindow.GetSizer().Add(G2frame.restrBook,1,wx.ALL|wx.EXPAND,1)
    # clear menu and menu pointers
    Pages = []    

    txt = 'Bond'
    BondRestr = wx.ScrolledWindow(G2frame.restrBook)
    G2frame.restrBook.AddPage(BondRestr,txt)
    Pages.append(txt)

    txt = 'Angle'
    AngleRestr = wx.ScrolledWindow(G2frame.restrBook)
    G2frame.restrBook.AddPage(AngleRestr,txt) 
    Pages.append(txt)
   
    txt = 'Plane'
    PlaneRestr = wx.ScrolledWindow(G2frame.restrBook)
    G2frame.restrBook.AddPage(PlaneRestr,txt)
    Pages.append(txt)

    txt = 'Chiral'
    ChiralRestr = wx.ScrolledWindow(G2frame.restrBook)
    G2frame.restrBook.AddPage(ChiralRestr,txt)
    Pages.append(txt)

    if 'macro' in General['Type']:
        txt = 'Torsion'
        TorsionRestr = wx.ScrolledWindow(G2frame.restrBook)
        G2frame.restrBook.AddPage(TorsionRestr,txt)
        Pages.append(txt)

        txt = 'Ramachandran'
        RamaRestr = wx.ScrolledWindow(G2frame.restrBook)
        G2frame.restrBook.AddPage(RamaRestr,txt)
        Pages.append(txt)

    txt = 'Chem. comp.'
    ChemCompRestr = wx.ScrolledWindow(G2frame.restrBook)
    G2frame.restrBook.AddPage(ChemCompRestr,txt)
    Pages.append(txt)
    
    txt = 'General'
    GeneralRestr = wx.ScrolledWindow(G2frame.restrBook)
    G2frame.restrBook.AddPage(GeneralRestr,txt)
    Pages.append(txt)
    
    if General['SH Texture']['Order']:
        txt = 'Texture'
        TextureRestr = wx.ScrolledWindow(G2frame.restrBook)
        G2frame.restrBook.AddPage(TextureRestr,txt)
        Pages.append(txt)

    UpdateBondRestr(restrData['Bond'])

    G2frame.restrBook.Bind(wx.aui.EVT_AUINOTEBOOK_PAGE_CHANGED, OnPageChanged)

    # fill page selection menu
    menu = G2frame.dataWindow.RestraintTab
    for page in Pages:
        if menu.FindItem(page) >= 0: continue # is tab already in menu?
        Id = wx.NewId()
        TabSelectionIdDict[Id] = page
        menu.Append(Id,page,'')
        G2frame.Bind(wx.EVT_MENU, OnSelectPage, id=Id)
