Ignore:
Timestamp:
May 24, 2020 5:19:15 PM (3 years ago)
Author:
toby
Message:

new code for locating RBs; redo GUI for RB extractor

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/GSASIIconstrGUI.py

    r4421 r4431  
    1616'''
    1717from __future__ import division, print_function
     18import platform
    1819import sys
    1920import copy
     
    4647WACV = wx.ALIGN_CENTER_VERTICAL
    4748
     49class G2BoolEditor(wg.GridCellBoolEditor):
     50    '''Substitute for wx.grid.GridCellBoolEditor except toggles
     51    grid items immediately when opened, updates grid & table contents after every
     52    item change
     53    '''
     54    def __init__(self):
     55        self.saveVals = None
     56        wx.grid.GridCellBoolEditor.__init__(self)
     57
     58
     59    def Create(self, parent, id, evtHandler):
     60        '''Create the editing control (wx.CheckBox) when cell is opened
     61        for edit
     62        '''
     63        self._tc = wx.CheckBox(parent, -1, "")
     64        self._tc.Bind(wx.EVT_CHECKBOX, self.onCheckSet)
     65        self.SetControl(self._tc)
     66        if evtHandler:
     67            self._tc.PushEventHandler(evtHandler)
     68
     69    def onCheckSet(self, event):
     70        '''Callback used when checkbox is toggled.
     71        Makes change to table immediately (creating event)
     72        '''
     73        if self.saveVals:
     74            self.ApplyEdit(*self.saveVals)
     75
     76       
     77    def SetSize(self, rect):
     78        '''Set position/size the edit control within the cell's rectangle.
     79        '''
     80#        self._tc.SetDimensions(rect.x, rect.y, rect.width+2, rect.height+2, # older
     81        self._tc.SetSize(rect.x, rect.y, rect.width+2, rect.height+2,
     82                               wx.SIZE_ALLOW_MINUS_ONE)
     83
     84    def BeginEdit(self, row, col, grid):
     85        '''Prepares the edit control by loading the initial
     86        value from the table (toggles it since you would not
     87        click on it if you were not planning to change it),
     88        buts saves the original, pre-change value.
     89        Makes change to table immediately.
     90        Saves the info needed to make updates in self.saveVals.
     91        Sets the focus.
     92        '''
     93        self.startValue = int(grid.GetTable().GetValue(row, col))
     94        self.saveVals = row, col, grid
     95        # invert state and set in editor
     96        if self.startValue:
     97            grid.GetTable().SetValue(row, col, 0)
     98            self._tc.SetValue(0)
     99        else:
     100            grid.GetTable().SetValue(row, col, 1)
     101            self._tc.SetValue(1)
     102        self._tc.SetFocus()
     103        self.ApplyEdit(*self.saveVals)
     104
     105    def EndEdit(self, row, col, grid, oldVal=None):
     106        '''End editing the cell.  This is supposed to
     107        return None if the value has not changed, but I am not
     108        sure that actually works.
     109        '''
     110        val = int(self._tc.GetValue())
     111        if val != oldVal:   #self.startValue:?
     112            return val
     113        else:
     114            return None
     115
     116    def ApplyEdit(self, row, col, grid):
     117        '''Save the value into the table, and create event.
     118        Called after EndEdit(), BeginEdit and onCheckSet.
     119        '''
     120        val = int(self._tc.GetValue())
     121        grid.GetTable().SetValue(row, col, val) # update the table
     122
     123    def Reset(self):
     124        '''Reset the value in the control back to its starting value.
     125        '''
     126        self._tc.SetValue(self.startValue)
     127
     128    def StartingClick(self):
     129        '''This seems to be needed for BeginEdit to work properly'''
     130        pass
     131
     132    def Destroy(self):
     133        "final cleanup"
     134        super(G2BoolEditor, self).Destroy()
     135
     136    def Clone(self):
     137        'required'
     138        return G2BoolEditor()
     139   
    48140class DragableRBGrid(wg.Grid):
    49141    '''Simple grid implentation for display of rigid body positions.
     
    62154        self.Bind(gridmovers.EVT_GRID_ROW_MOVE, self.OnRowMove, self)
    63155        self.SetColSize(0, 60)
    64         self.SetColSize(1, 35)
    65         self.SetColSize(5, 40)
     156        self.SetColSize(1, 40)
     157        self.SetColSize(2, 35)
    66158        for r in range(len(rb['RBlbls'])):
    67159            self.SetReadOnly(r,0,isReadOnly=True)
    68             self.SetCellEditor(r,2, wg.GridCellFloatEditor())
     160            self.SetCellEditor(r, 1, G2BoolEditor())           
     161            self.SetCellRenderer(r, 1, wg.GridCellBoolRenderer())
     162            self.SetReadOnly(r,2,isReadOnly=True)
    69163            self.SetCellEditor(r,3, wg.GridCellFloatEditor())
    70164            self.SetCellEditor(r,4, wg.GridCellFloatEditor())
     165            self.SetCellEditor(r,6, wg.GridCellFloatEditor())
    71166
    72167    def OnRowMove(self,evt):
     
    89184    def __init__(self,rb,onChange):
    90185        wg.GridTableBase.__init__(self)
    91         self.colLabels = ['Label','Type','x','y','z','Select']
     186        self.colLabels = ['Label','Select','Type','x','y','z']
    92187        self.coords = rb['RBcoords']
    93188        self.labels = rb['RBlbls']
     
    109204            return self.labels[row]
    110205        elif col == 1:
     206            return int(self.select[row])
     207        elif col == 2:
    111208            return self.types[row]
    112         elif col < 5:
    113             return '{:.5f}'.format(self.coords[row][col-2])
    114         elif col == 5:
    115             return self.select[row]
     209        else:
     210            return '{:.5f}'.format(self.coords[row][col-3])
    116211    def SetValue(self, row, col, value):
    117212        row = self.index[row]
    118         if col == 0:
    119             self.labels[row] = value
    120         elif col == 1:
    121             self.types[row] = value
    122         elif col < 5:
    123             self.coords[row][col-2] = float(value)
    124         elif col == 5:
    125             self.select[row] = value
     213        try:
     214            if col == 0:
     215                self.labels[row] = value
     216            elif col == 1:
     217                self.select[row] = int(value)
     218            elif col == 2:
     219                self.types[row] = value
     220            else:
     221                self.coords[row][col-3] = float(value)
     222        except:
     223            pass
    126224        if self.onChange:
    127225            self.onChange()
    128     # implement boolean for selection
    129     def GetTypeName(self, row, col):
    130         if col==5:
    131             return wg.GRID_VALUE_BOOL
    132         else:
    133             return wg.GRID_VALUE_STRING
    134     def CanGetValueAs(self, row, col, typeName):
    135         if col==5 and typeName != wg.GRID_VALUE_BOOL:
    136             return False
    137         return True
    138 
    139226    # Display column & row labels
    140227    def GetColLabelValue(self, col):
     
    20272114            def onSetAll(event):
    20282115                'Set all atoms as selected'
     2116                grid.completeEdits()
    20292117                for i in range(len(rd.Phase['RBselection'])):
    2030                     rd.Phase['RBselection'][i] = True
     2118                    rd.Phase['RBselection'][i] = 1 # table needs 0/1 for T/F
    20312119                grid.ForceRefresh()
    20322120                UpdateDraw()
     
    20362124                grid.completeEdits()
    20372125                for i in range(len(rd.Phase['RBselection'])):
    2038                     rd.Phase['RBselection'][i] = not rd.Phase['RBselection'][i]
     2126                    rd.Phase['RBselection'][i] = int(not rd.Phase['RBselection'][i])
    20392127                grid.ForceRefresh()
    20402128                UpdateDraw()
     
    20632151                        center += rd.Phase['RBcoords'][i]
    20642152                if not count:
     2153                    G2G.G2MessageBox(G2frame,'No atoms selected',
     2154                                    'Selection required')
    20652155                    return
    2066                 XP = center/count
    2067                 if np.sqrt(sum(XP**2)) < 0.1:
     2156                XYZP = center/count
     2157                if np.sqrt(sum(XYZP**2)) < 0.1:
    20682158                    G2G.G2MessageBox(G2frame,
    20692159                            'The selected atom(s) are too close to the origin',
    20702160                            'near origin')
    20712161                    return
    2072                 XP /= np.sqrt(np.sum(XP**2))
    2073                 Z = np.array((0,0,1.))
    2074                 YP = np.cross(Z,XP)
    2075                 ZP = np.cross(XP,YP)
     2162                if bntOpts['direction'] == 'y':
     2163                    YP = XYZP / np.sqrt(np.sum(XYZP**2))
     2164                    ZP = np.cross((1,0,0),YP)
     2165                    if sum(ZP*ZP) < .1: # pathological condition: Y' along X
     2166                        ZP = np.cross((0,0,1),YP)
     2167                    XP = np.cross(YP,ZP)
     2168                elif bntOpts['direction'] == 'z':
     2169                    ZP = XYZP / np.sqrt(np.sum(XYZP**2))
     2170                    XP = np.cross((0,1,0),ZP)
     2171                    if sum(XP*XP) < .1: # pathological condition: X' along Y
     2172                        XP = np.cross((0,0,1),ZP)
     2173                    YP = np.cross(ZP,XP)
     2174                else:
     2175                    XP = XYZP / np.sqrt(np.sum(XYZP**2))
     2176                    YP = np.cross((0,0,1),XP)
     2177                    if sum(YP*YP) < .1: # pathological condition: X' along Z
     2178                        YP = np.cross((0,1,0),XP)
     2179                    ZP = np.cross(XP,YP)
    20762180                trans = np.array((XP,YP,ZP))
    20772181                # update atoms in place
     
    20802184                UpdateDraw()
    20812185
    2082             def onSetPlane(event):
     2186            def onSetPlane(event): 
    20832187                '''Compute least-squares plane for selected atoms;
    20842188                move atoms so that LS plane aligned with x-y plane,
     
    20862190                '''
    20872191                grid.completeEdits()
    2088                 #X,Y,Z = rd.Phase['RBcoords'][rd.Phase['RBselection']].T
    2089                 XYZ = rd.Phase['RBcoords'][rd.Phase['RBselection']]
    2090                 Z = copy.copy(XYZ[:,2])
    2091                 if len(Z) < 3:
     2192                selList = [i==1 for i in rd.Phase['RBselection']]
     2193                XYZ = rd.Phase['RBcoords'][selList]
     2194                if len(XYZ) < 3:
    20922195                    G2G.G2MessageBox(G2frame,'A plane requires three or more atoms',
    20932196                                     'Need more atoms')
    20942197                    return
    2095                 XY0 = copy.copy(XYZ)
    2096                 XY0[:,2] = 1
     2198                # fit 3 ways (in case of singularity) and take result with lowest residual
     2199                X,Y,Z = [XYZ[:,i] for i in (0,1,2)]
     2200                XZ = copy.copy(XYZ)
     2201                XZ[:,1] = 1
     2202                (a,d,b), resd, rank, sing = nl.lstsq(XZ, -Y)
     2203                resid_min = resd
     2204                normal = a,1,b
     2205                YZ = copy.copy(XYZ)
     2206                YZ[:,0] = 1
     2207                (d,a,b), resd, rank, sing = nl.lstsq(YZ, -X)
     2208                if resid_min > resd:
     2209                    resid_min = resd
     2210                    normal = 1,a,b
     2211                XY = copy.copy(XYZ)
     2212                XY[:,2] = 1
     2213                (a,b,d), resd, rank, sing = nl.lstsq(XY, -Z)
     2214                if resid_min > resd:
     2215                    resid_min = resd
     2216                    normal = a,b,1
    20972217                # solve for  ax + bx + z + c = 0 or equivalently ax + bx + c = -z
    2098                 try:
    2099                     (a,b,c), resd, rank, sing = nl.lstsq(XY0, -Z)
    2100                 except:
    2101                     G2G.G2MessageBox(G2frame,
    2102                             'Error computing plane; are atoms in a line?',
    2103                             'Computation error')
     2218                # try:
     2219                # except:
     2220                #     G2G.G2MessageBox(G2frame,
     2221                #             'Error computing plane; are atoms in a line?',
     2222                #             'Computation error')
     2223                #     return
     2224                if bntOpts['plane'] == 'xy':
     2225                    # new coordinate system is
     2226                    #   ZP, z' normal to plane
     2227                    #   YP, y' = z' cross x (= [0,1,-b])
     2228                    #   XP, x' = (z' cross x) cross z'
     2229                    # this puts XP as close as possible to X with XP & YP in plane
     2230                    ZP = np.array(normal)
     2231                    ZP /= np.sqrt(np.sum(ZP**2))
     2232                    YP = np.cross(ZP,[1,0,0])
     2233                    if sum(YP*YP) < .1: # pathological condition: z' along x
     2234                        YP = np.cross(ZP,[0,1,0])
     2235                    YP /= np.sqrt(np.sum(YP**2))
     2236                    XP = np.cross(YP,ZP)
     2237                elif bntOpts['plane'] == 'yz':
     2238                    # new coordinate system is
     2239                    #   XP, x' normal to plane
     2240                    #   ZP, z' = x' cross y
     2241                    #   YP, y' = (x' cross y) cross x'
     2242                    # this puts y' as close as possible to y with z' & y' in plane
     2243                    XP = np.array(normal)
     2244                    XP /= np.sqrt(np.sum(XP**2))
     2245                    ZP = np.cross(XP,[0,1,0])
     2246                    if sum(ZP*ZP) < .1: # pathological condition: x' along y
     2247                        ZP = np.cross(XP,(0,0,1))
     2248                    ZP /= np.sqrt(np.sum(ZP**2))
     2249                    YP = np.cross(ZP,XP)
     2250                elif bntOpts['plane'] == 'xz':
     2251                    # new coordinate system is
     2252                    #   YP, y' normal to plane
     2253                    #   ZP, z' = x cross y'
     2254                    #   XP, y' = (x cross y') cross z'
     2255                    # this puts XP as close as possible to X with XP & YP in plane
     2256                    YP = np.array(normal)
     2257                    YP /= np.sqrt(np.sum(YP**2))
     2258                    ZP = np.cross([1,0,0],YP)
     2259                    if sum(ZP*ZP) < .1: # pathological condition: y' along x
     2260                        ZP = np.cross([0,1,0],YP)
     2261                    ZP /= np.sqrt(np.sum(ZP**2))
     2262                    XP = np.cross(YP,ZP)
     2263                else:
     2264                    print('unexpected plane',bntOpts['plane'])
    21042265                    return
    2105                 # new coordinate system is z' (zp, normal to plane = [a,b,1]),
    2106                 # y' = z' cross x (YP, = [0,1,-b])
    2107                 # x' = (z' cross x) cross z'
    2108                 # this puts XP as close as possible to X with XP & YP in plane
    2109                 ZP = np.array([a,b,1])
    2110                 ZP /= np.sqrt(np.sum(ZP**2))
    2111                 YP = np.array([0,1,-b])
    2112                 YP /= np.sqrt(np.sum(YP**2))
    2113                 XP = np.cross(YP,ZP)
    21142266                trans = np.array((XP,YP,ZP))
    21152267                # update atoms in place
     
    21842336
    21852337            rd.Phase['RBindex'] = list(range(len(rd.Phase['RBtypes'])))
    2186             rd.Phase['RBselection'] = len(rd.Phase['RBtypes']) * [True]
     2338            rd.Phase['RBselection'] = len(rd.Phase['RBtypes']) * [1]
    21872339            rbData = MakeVectorBody()
    21882340            DrawCallback = G2plt.PlotRigidBody(G2frame,'Vector',
     
    21902342
    21912343            mainSizer = wx.BoxSizer(wx.HORIZONTAL)
    2192             gridSizer = wx.BoxSizer(wx.VERTICAL)
    2193             grid = DragableRBGrid(RBImpPnl,rd.Phase,UpdateDraw)
    2194             gridSizer.Add(grid)
    2195             gridSizer.Add(
     2344            btnSizer = wx.BoxSizer(wx.VERTICAL)
     2345            btnSizer.Add(
    21962346                wx.StaticText(RBImpPnl,wx.ID_ANY,'Reorder atoms by dragging'),
    21972347                0,wx.ALL)
    2198             mainSizer.Add(gridSizer)
    2199             mainSizer.Add((5,5))
    2200             btnSizer = wx.BoxSizer(wx.VERTICAL)
    2201             btn = wx.Button(RBImpPnl, wx.ID_OK, 'Set All')
     2348            btnSizer.Add((-1,15))
     2349            btn = wx.Button(RBImpPnl, wx.ID_ANY, 'Set All')
    22022350            btn.Bind(wx.EVT_BUTTON,onSetAll)
    22032351            btnSizer.Add(btn,0,wx.ALIGN_CENTER)
    2204             btn = wx.Button(RBImpPnl, wx.ID_OK, 'Toggle')
     2352            btn = wx.Button(RBImpPnl, wx.ID_ANY, 'Toggle')
    22052353            btn.Bind(wx.EVT_BUTTON,onToggle)
    22062354            btnSizer.Add(btn,0,wx.ALIGN_CENTER)
     
    22102358                0,wx.ALL)
    22112359            btnSizer.Add((-1,5))
    2212             btn = wx.Button(RBImpPnl, wx.ID_OK, 'Set origin')
     2360            btn = wx.Button(RBImpPnl, wx.ID_ANY, 'Set origin')
    22132361            btn.Bind(wx.EVT_BUTTON,onSetOrigin)
    22142362            btnSizer.Add(btn,0,wx.ALIGN_CENTER)
    2215             btn = wx.Button(RBImpPnl, wx.ID_OK, 'Place in xy plane')
     2363
     2364            bntOpts = {'plane':'xy','direction':'x'}
     2365            inSizer = wx.BoxSizer(wx.HORIZONTAL)
     2366            btn = wx.Button(RBImpPnl, wx.ID_ANY, 'Place in plane')
    22162367            btn.Bind(wx.EVT_BUTTON,onSetPlane)
    2217             btnSizer.Add(btn,0,wx.ALIGN_CENTER)
    2218             btn = wx.Button(RBImpPnl, wx.ID_OK, 'Define selection as X')
     2368            inSizer.Add(btn)
     2369            inSizer.Add(G2G.G2ChoiceButton(RBImpPnl,('xy','yz','xz'),
     2370                                           None,None,bntOpts,'plane'))
     2371            btnSizer.Add(inSizer,0,wx.ALIGN_CENTER)
     2372           
     2373            inSizer = wx.BoxSizer(wx.HORIZONTAL)
     2374            btn = wx.Button(RBImpPnl, wx.ID_ANY, 'Define as')
    22192375            btn.Bind(wx.EVT_BUTTON,onSetX)
    2220             btnSizer.Add(btn,0,wx.ALIGN_CENTER)
     2376            inSizer.Add(btn)
     2377            inSizer.Add(G2G.G2ChoiceButton(RBImpPnl,('x','y','z'),
     2378                                           None,None,bntOpts,'direction'))
     2379            btnSizer.Add(inSizer,0,wx.ALIGN_CENTER)
     2380           
    22212381            btnSizer.Add((-1,15))
    22222382            btnSizer.Add(
     
    22242384                0,wx.ALL)
    22252385            btnSizer.Add((-1,5))
    2226             btn = wx.Button(RBImpPnl, wx.ID_OK, 'a Vector Body')
     2386            btn = wx.Button(RBImpPnl, wx.ID_ANY, 'a Vector Body')
    22272387            btn.Bind(wx.EVT_BUTTON,onAddVector)
    22282388            btnSizer.Add(btn,0,wx.ALIGN_CENTER)
    2229             btn = wx.Button(RBImpPnl, wx.ID_OK, 'a Residue Body')
     2389            btn = wx.Button(RBImpPnl, wx.ID_ANY, 'a Residue Body')
    22302390            btn.Bind(wx.EVT_BUTTON,onAddResidue)
    22312391            btnSizer.Add(btn,0,wx.ALIGN_CENTER)
     
    22362396
    22372397            mainSizer.Add(btnSizer)
     2398            mainSizer.Add((5,5))
     2399            grid = DragableRBGrid(RBImpPnl,rd.Phase,UpdateDraw)
     2400            mainSizer.Add(grid)
    22382401            RBImpPnl.SetSizer(mainSizer,True)
    22392402            mainSizer.Layout()   
     
    22992462            return rb
    23002463
     2464        # too lazy to figure out why wx crashes
     2465        if wx.__version__.split('.')[0] != '4':
     2466            wx.MessageBox('Sorry, wxPython 4.x is required to run this command',
     2467                                  caption='Update Python',
     2468                                  style=wx.ICON_EXCLAMATION)
     2469            return
     2470        if platform.python_version()[:1] == '2':
     2471            wx.MessageBox('Sorry, Python >=3.x is required to run this command',
     2472                                  caption='Update Python',
     2473                                  style=wx.ICON_EXCLAMATION)
     2474            return
     2475
    23012476        # get importer type and a phase file of that type
    23022477        G2sc.LoadG2fil()
     
    23042479        dlg = G2G.G2SingleChoiceDialog(G2frame,'Select the format of the file',
    23052480                                     'select format',choices)
     2481        dlg.CenterOnParent()
    23062482        try:
    23072483            if dlg.ShowModal() == wx.ID_OK:
Note: See TracChangeset for help on using the changeset viewer.