Changeset 4431 for trunk/GSASIIconstrGUI.py
- Timestamp:
- May 24, 2020 5:19:15 PM (3 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/GSASIIconstrGUI.py
r4421 r4431 16 16 ''' 17 17 from __future__ import division, print_function 18 import platform 18 19 import sys 19 20 import copy … … 46 47 WACV = wx.ALIGN_CENTER_VERTICAL 47 48 49 class 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 48 140 class DragableRBGrid(wg.Grid): 49 141 '''Simple grid implentation for display of rigid body positions. … … 62 154 self.Bind(gridmovers.EVT_GRID_ROW_MOVE, self.OnRowMove, self) 63 155 self.SetColSize(0, 60) 64 self.SetColSize(1, 35)65 self.SetColSize( 5, 40)156 self.SetColSize(1, 40) 157 self.SetColSize(2, 35) 66 158 for r in range(len(rb['RBlbls'])): 67 159 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) 69 163 self.SetCellEditor(r,3, wg.GridCellFloatEditor()) 70 164 self.SetCellEditor(r,4, wg.GridCellFloatEditor()) 165 self.SetCellEditor(r,6, wg.GridCellFloatEditor()) 71 166 72 167 def OnRowMove(self,evt): … … 89 184 def __init__(self,rb,onChange): 90 185 wg.GridTableBase.__init__(self) 91 self.colLabels = ['Label',' Type','x','y','z','Select']186 self.colLabels = ['Label','Select','Type','x','y','z'] 92 187 self.coords = rb['RBcoords'] 93 188 self.labels = rb['RBlbls'] … … 109 204 return self.labels[row] 110 205 elif col == 1: 206 return int(self.select[row]) 207 elif col == 2: 111 208 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]) 116 211 def SetValue(self, row, col, value): 117 212 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 126 224 if self.onChange: 127 225 self.onChange() 128 # implement boolean for selection129 def GetTypeName(self, row, col):130 if col==5:131 return wg.GRID_VALUE_BOOL132 else:133 return wg.GRID_VALUE_STRING134 def CanGetValueAs(self, row, col, typeName):135 if col==5 and typeName != wg.GRID_VALUE_BOOL:136 return False137 return True138 139 226 # Display column & row labels 140 227 def GetColLabelValue(self, col): … … 2027 2114 def onSetAll(event): 2028 2115 'Set all atoms as selected' 2116 grid.completeEdits() 2029 2117 for i in range(len(rd.Phase['RBselection'])): 2030 rd.Phase['RBselection'][i] = True2118 rd.Phase['RBselection'][i] = 1 # table needs 0/1 for T/F 2031 2119 grid.ForceRefresh() 2032 2120 UpdateDraw() … … 2036 2124 grid.completeEdits() 2037 2125 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]) 2039 2127 grid.ForceRefresh() 2040 2128 UpdateDraw() … … 2063 2151 center += rd.Phase['RBcoords'][i] 2064 2152 if not count: 2153 G2G.G2MessageBox(G2frame,'No atoms selected', 2154 'Selection required') 2065 2155 return 2066 X P = center/count2067 if np.sqrt(sum(X P**2)) < 0.1:2156 XYZP = center/count 2157 if np.sqrt(sum(XYZP**2)) < 0.1: 2068 2158 G2G.G2MessageBox(G2frame, 2069 2159 'The selected atom(s) are too close to the origin', 2070 2160 'near origin') 2071 2161 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) 2076 2180 trans = np.array((XP,YP,ZP)) 2077 2181 # update atoms in place … … 2080 2184 UpdateDraw() 2081 2185 2082 def onSetPlane(event): 2186 def onSetPlane(event): 2083 2187 '''Compute least-squares plane for selected atoms; 2084 2188 move atoms so that LS plane aligned with x-y plane, … … 2086 2190 ''' 2087 2191 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: 2092 2195 G2G.G2MessageBox(G2frame,'A plane requires three or more atoms', 2093 2196 'Need more atoms') 2094 2197 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 2097 2217 # 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']) 2104 2265 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 plane2109 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)2114 2266 trans = np.array((XP,YP,ZP)) 2115 2267 # update atoms in place … … 2184 2336 2185 2337 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] 2187 2339 rbData = MakeVectorBody() 2188 2340 DrawCallback = G2plt.PlotRigidBody(G2frame,'Vector', … … 2190 2342 2191 2343 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( 2196 2346 wx.StaticText(RBImpPnl,wx.ID_ANY,'Reorder atoms by dragging'), 2197 2347 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') 2202 2350 btn.Bind(wx.EVT_BUTTON,onSetAll) 2203 2351 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') 2205 2353 btn.Bind(wx.EVT_BUTTON,onToggle) 2206 2354 btnSizer.Add(btn,0,wx.ALIGN_CENTER) … … 2210 2358 0,wx.ALL) 2211 2359 btnSizer.Add((-1,5)) 2212 btn = wx.Button(RBImpPnl, wx.ID_ OK, 'Set origin')2360 btn = wx.Button(RBImpPnl, wx.ID_ANY, 'Set origin') 2213 2361 btn.Bind(wx.EVT_BUTTON,onSetOrigin) 2214 2362 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') 2216 2367 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') 2219 2375 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 2221 2381 btnSizer.Add((-1,15)) 2222 2382 btnSizer.Add( … … 2224 2384 0,wx.ALL) 2225 2385 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') 2227 2387 btn.Bind(wx.EVT_BUTTON,onAddVector) 2228 2388 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') 2230 2390 btn.Bind(wx.EVT_BUTTON,onAddResidue) 2231 2391 btnSizer.Add(btn,0,wx.ALIGN_CENTER) … … 2236 2396 2237 2397 mainSizer.Add(btnSizer) 2398 mainSizer.Add((5,5)) 2399 grid = DragableRBGrid(RBImpPnl,rd.Phase,UpdateDraw) 2400 mainSizer.Add(grid) 2238 2401 RBImpPnl.SetSizer(mainSizer,True) 2239 2402 mainSizer.Layout() … … 2299 2462 return rb 2300 2463 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 2301 2476 # get importer type and a phase file of that type 2302 2477 G2sc.LoadG2fil() … … 2304 2479 dlg = G2G.G2SingleChoiceDialog(G2frame,'Select the format of the file', 2305 2480 'select format',choices) 2481 dlg.CenterOnParent() 2306 2482 try: 2307 2483 if dlg.ShowModal() == wx.ID_OK:
Note: See TracChangeset
for help on using the changeset viewer.