Statistiques
| Révision :

root / ase / gui / gui.py @ 12

Historique | Voir | Annoter | Télécharger (41,34 ko)

1
# husk:
2
# Exit*2?  remove pylab.show()
3
# close button
4
# DFT
5
# ADOS
6
# grey-out stuff after one second: vmd, rasmol, ...
7
# Show with ....
8
# rasmol: set same rotation as ag
9
# Graphs: save, Python, 3D
10
# start from python (interactive mode?)
11
# ascii-art option (colored)|
12
# option -o (output) and -f (force overwrite)
13
# surfacebuilder
14
# screen-dump
15
# icon
16
# ag-community-server
17
# translate option: record all translations, 
18
# and check for missing translations.
19

    
20
#TODO: Add possible way of choosing orinetations. \
21
#TODO: Two atoms defines a direction, three atoms their normal does
22
#TODO: Align orientations chosen in Rot_selected v unselcted
23
#TODO: Get the atoms_rotate_0 thing string
24
#TODO: Use set atoms instead og the get atoms
25
#TODO: Arrow keys will decide how the orientation changes
26
#TODO: Undo redo que should be implemented
27
#TODO: Update should have possibility to change positions
28
#TODO: Window for rotation modes and move moves which can be chosen
29
#TODO: WHen rotate and move / hide the movie menu
30

    
31
import os
32
import weakref
33
import numpy as np
34

    
35
import gtk
36
from ase.gui.view import View
37
from ase.gui.status import Status
38
from ase.gui.widgets import pack, help, Help, oops
39
from ase.gui.languages import translate as _
40

    
41
from ase.gui.settings import Settings
42
from ase.gui.surfaceslab import SetupSurfaceSlab
43
from ase.gui.nanoparticle import SetupNanoparticle
44
from ase.gui.nanotube import SetupNanotube
45
from ase.gui.graphene import SetupGraphene
46
from ase.gui.calculator import SetCalculator
47
from ase.gui.energyforces import EnergyForces
48
from ase.gui.minimize import Minimize
49
from ase.gui.scaling import HomogeneousDeformation
50

    
51
ui_info = """\
52
<ui>
53
  <menubar name='MenuBar'>
54
    <menu action='FileMenu'>
55
      <menuitem action='Open'/>
56
      <menuitem action='New'/>
57
      <menuitem action='Save'/>
58
      <separator/>
59
      <menuitem action='Quit'/>
60
    </menu>
61
    <menu action='EditMenu'>
62
      <menuitem action='SelectAll'/>
63
      <menuitem action='Invert'/>
64
      <menuitem action='SelectConstrained'/>
65
      <separator/>
66
      <menuitem action='Modify'/>
67
      <menuitem action='AddAtoms'/>
68
      <menuitem action='DeleteAtoms'/>
69
      <separator/>      
70
      <menuitem action='First'/>
71
      <menuitem action='Previous'/>
72
      <menuitem action='Next'/>
73
      <menuitem action='Last'/>
74
    </menu>
75
    <menu action='ViewMenu'>
76
      <menuitem action='ShowUnitCell'/>
77
      <menuitem action='ShowAxes'/>
78
      <menuitem action='ShowBonds'/>
79
      <separator/>
80
      <menuitem action='Repeat'/>
81
      <menuitem action='Rotate'/>
82
      <menuitem action='Focus'/>
83
      <menuitem action='ZoomIn'/>
84
      <menuitem action='ZoomOut'/>
85
      <menuitem action='Settings'/>
86
      <menuitem action='VMD'/>
87
      <menuitem action='RasMol'/>
88
      <menuitem action='XMakeMol'/>
89
      <menuitem action='Avogadro'/>
90
    </menu>
91
    <menu action='ToolsMenu'>
92
      <menuitem action='Graphs'/>
93
      <menuitem action='Movie'/>
94
      <menuitem action='EModify'/>
95
      <menuitem action='Constraints'/>
96
      <menuitem action='MoveAtoms'/>
97
      <menuitem action='RotateAtoms'/>
98
      <menuitem action='OrientAtoms'/>
99
      <menuitem action='DFT'/>
100
      <menuitem action='NEB'/>
101
      <menuitem action='BulkModulus'/>
102
    </menu>
103
    <menu action='SetupMenu'>
104
      <menuitem action='Surface'/>
105
      <menuitem action='Nanoparticle'/>
106
      <menuitem action='Graphene'/>
107
      <menuitem action='Nanotube'/>
108
    </menu>
109
    <menu action='CalculateMenu'>
110
      <menuitem action='SetCalculator'/>
111
      <separator/>
112
      <menuitem action='EnergyForces'/>
113
      <menuitem action='Minimize'/>
114
      <menuitem action='Scaling'/>
115
    </menu>
116
    <menu action='HelpMenu'>
117
      <menuitem action='About'/>
118
      <menuitem action='Webpage'/>
119
      <menuitem action='Debug'/>
120
    </menu>
121
  </menubar>
122
</ui>"""
123

    
124
class GUI(View, Status):
125
    def __init__(self, images, rotations='', show_unit_cell=True,
126
                 show_bonds=False):
127
        self.images = images
128
        self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
129
        #self.window.set_icon(gtk.gdk.pixbuf_new_from_file('guiase.png'))
130
        self.window.set_position(gtk.WIN_POS_CENTER)
131
        #self.window.connect("destroy", lambda w: gtk.main_quit())
132
        self.window.connect('delete_event', self.exit)
133
        vbox = gtk.VBox()
134
        self.window.add(vbox)
135
        if gtk.pygtk_version < (2, 12):
136
            self.set_tip = gtk.Tooltips().set_tip
137

    
138
        actions = gtk.ActionGroup("Actions")
139
        actions.add_actions([
140
            ('FileMenu', None, '_File'),
141
            ('EditMenu', None, '_Edit'),
142
            ('ViewMenu', None, '_View'  ),
143
            ('ToolsMenu', None, '_Tools'),
144
            ('SetupMenu', None, '_Setup'),
145
            ('CalculateMenu', None, '_Calculate'),
146
            ('HelpMenu', None, '_Help'),
147
            ('Open', gtk.STOCK_OPEN, '_Open', '<control>O',
148
             'Create a new file',
149
             self.open),
150
             ('New', gtk.STOCK_NEW, '_New', '<control>N',
151
             'New ase.gui window',
152
             lambda widget: os.system('ag &')),
153
            ('Save', gtk.STOCK_SAVE, '_Save', '<control>S',
154
             'Save current file',
155
             self.save),
156
            ('Quit', gtk.STOCK_QUIT, '_Quit', '<control>Q',
157
             'Quit',
158
             self.exit),
159
            ('SelectAll', None, 'Select _all', None,
160
             '',
161
             self.select_all),
162
            ('Invert', None, '_Invert selection', None,
163
             '',
164
             self.invert_selection),
165
            ('SelectConstrained', None, 'Select _constrained atoms', None,
166
             '',
167
             self.select_constrained_atoms),
168
             ('Modify', None, '_Modify', '<control>Y',
169
              'Change tags, moments and atom types of the selected atoms',
170
              self.modify_atoms),
171
             ('AddAtoms', None, '_Add atoms', '<control>A',
172
              'Insert or import atoms and molecules',
173
              self.add_atoms),
174
             ('DeleteAtoms', None, '_Delete selected atoms', 'BackSpace',
175
              'Deletes the selected atoms',
176
              self.delete_selected_atoms),             
177
            ('First', gtk.STOCK_GOTO_FIRST, '_First image', 'Home',
178
             '',
179
             self.step),
180
            ('Previous', gtk.STOCK_GO_BACK, '_Previous image', 'Page_Up',
181
             '',
182
             self.step),
183
            ('Next', gtk.STOCK_GO_FORWARD, '_Next image', 'Page_Down',
184
             '',
185
             self.step),
186
            ('Last', gtk.STOCK_GOTO_LAST, '_Last image', 'End',
187
             '',
188
             self.step),
189
            ('Repeat', None, 'Repeat ...', None,
190
             '',
191
             self.repeat_window),
192
            ('Rotate', None, 'Rotate ...', None,
193
             '',
194
             self.rotate_window),
195
            ('Focus', gtk.STOCK_ZOOM_FIT, 'Focus', 'F',
196
             '',
197
             self.focus),
198
            ('ZoomIn', gtk.STOCK_ZOOM_IN, 'Zoom in', 'plus',
199
             '',
200
             self.zoom),
201
            ('ZoomOut', gtk.STOCK_ZOOM_OUT, 'Zoom out', 'minus',
202
             '',
203
             self.zoom),
204
            ('Settings', gtk.STOCK_PREFERENCES, 'Settings ...', None,
205
             '',
206
             self.settings),
207
            ('VMD', None, 'VMD', None,
208
             '',
209
             self.external_viewer),
210
            ('RasMol', None, 'RasMol', None,
211
             '',
212
             self.external_viewer),
213
            ('XMakeMol', None, 'xmakemol', None,
214
             '',
215
             self.external_viewer),
216
            ('Avogadro', None, 'avogadro', None,
217
             '',
218
             self.external_viewer),
219
            ('Graphs', None, 'Graphs ...', None,
220
             '',
221
             self.plot_graphs),
222
            ('Movie', None, 'Movie ...', None,
223
             '',
224
             self.movie),
225
            ('EModify', None, 'Expert modify ...', None,
226
             '',
227
             self.execute),
228
            ('Constraints', None, 'Constraints ...', None,
229
             '',
230
             self.constraints_window),
231
            ('DFT', None, 'DFT ...', None,
232
             '',
233
             self.dft_window),
234
            ('NEB', None, 'NE_B', None,
235
             '',
236
             self.NEB),
237
            ('BulkModulus', None, 'B_ulk Modulus', None,
238
             '',
239
             self.bulk_modulus),
240
            ('Bulk', None, '_Bulk Crystal', None,
241
             "Create a bulk crystal with arbitrary orientation",
242
             self.bulk_window),
243
            ('Surface', None, '_Surface slab', None,
244
             "Create the most common surfaces",
245
             self.surface_window),
246
            ('Nanoparticle', None, '_Nanoparticle', None,
247
             "Create a crystalline nanoparticle",
248
             self.nanoparticle_window),
249
            ('Nanotube', None, 'Nano_tube', None,
250
             "Create a nanotube",
251
             self.nanotube_window),
252
            ('Graphene', None, 'Graphene', None,
253
             "Create a graphene sheet or nanoribbon",
254
             self.graphene_window),
255
            ('SetCalculator', None, 'Set _Calculator', None,
256
             "Set a calculator used in all calculation modules",
257
             self.calculator_window),
258
            ('EnergyForces', None, '_Energy and Forces', None,
259
             "Calculate energy and forces",
260
             self.energy_window),
261
            ('Minimize', None, 'Energy _Minimization', None,
262
             "Minimize the energy",
263
             self.energy_minimize_window),
264
            ('Scaling', None, 'Scale system', None,
265
             "Deform system by scaling it",
266
             self.scaling_window),
267
            ('About', None, '_About', None,
268
             None,
269
             self.about),
270
            ('Webpage', gtk.STOCK_HELP, 'Webpage ...', None, None, webpage),
271
            ('Debug', None, 'Debug ...', None, None, self.debug)])
272
        actions.add_toggle_actions([
273
            ('ShowUnitCell', None, 'Show _unit cell', '<control>U',
274
             'Bold',
275
             self.toggle_show_unit_cell,
276
             show_unit_cell > 0),
277
            ('ShowAxes', None, 'Show _axes', None,
278
             'Bold',
279
             self.toggle_show_axes,
280
             True),
281
            ('ShowBonds', None, 'Show _bonds', '<control>B',
282
             'Bold',
283
             self.toggle_show_bonds,
284
             show_bonds),
285
            ('MoveAtoms', None, '_Move atoms', '<control>M',
286
             'Bold',
287
             self.toggle_move_mode,
288
             False),
289
            ('RotateAtoms', None, '_Rotate atoms', '<control>R',
290
             'Bold',
291
             self.toggle_rotate_mode,
292
             False),
293
            ('OrientAtoms', None, 'Orien_t atoms', '<control>T',
294
             'Bold',
295
             self.toggle_orient_mode,
296
             False)             
297
            ])
298
        self.ui = ui = gtk.UIManager()
299
        ui.insert_action_group(actions, 0)
300
        self.window.add_accel_group(ui.get_accel_group())
301

    
302
        try:
303
            mergeid = ui.add_ui_from_string(ui_info)
304
        except gobject.GError, msg:
305
            print 'building menus failed: %s' % msg
306

    
307
        vbox.pack_start(ui.get_widget('/MenuBar'), False, False, 0)
308
        
309
        View.__init__(self, vbox, rotations)
310
        Status.__init__(self, vbox)
311
        vbox.show()
312
        #self.window.set_events(gtk.gdk.BUTTON_PRESS_MASK)
313
        self.window.connect('key-press-event', self.scroll)
314
        self.window.connect('scroll_event', self.scroll_event)
315
        self.window.show()
316
        self.graphs = []       # List of open pylab windows
317
        self.graph_wref = []   # List of weakrefs to Graph objects
318
        self.movie_window = None
319
        self.vulnerable_windows = []
320
        self.simulation = {}   # Used by modules on Calculate menu.
321
        self.module_state = {} # Used by modules to store their state.
322

    
323
    def run(self, expr=None):
324
        self.set_colors()
325
        self.set_coordinates(self.images.nimages - 1, focus=True)
326

    
327
        if self.images.nimages > 1:
328
            self.movie()
329

    
330
        if expr is None and not np.isnan(self.images.E[0]):
331
            expr = 'i, e - E[-1]'
332
            
333
        if expr is not None and expr != '' and self.images.nimages > 1:
334
            self.plot_graphs(expr=expr)
335

    
336
        gtk.main()
337
    
338
            
339
    def step(self, action):
340
        d = {'First': -10000000,
341
             'Previous': -1,
342
             'Next': 1,
343
             'Last': 10000000}[action.get_name()]
344
        i = max(0, min(self.images.nimages - 1, self.frame + d))
345
        self.set_frame(i)
346
        if self.movie_window is not None:
347
            self.movie_window.frame_number.value = i
348
            
349
    def _do_zoom(self, x):
350
        """Utility method for zooming"""
351
        self.scale *= x
352
        self.draw()
353
        
354
    def zoom(self, action):
355
        """Zoom in/out on keypress or clicking menu item"""
356
        x = {'ZoomIn': 1.2, 'ZoomOut':1 / 1.2}[action.get_name()]
357
        self._do_zoom(x)
358

    
359
    def scroll_event(self, window, event):
360
        """Zoom in/out when using mouse wheel"""
361
        x = 1.0
362
        if event.direction == gtk.gdk.SCROLL_UP:
363
            x = 1.2
364
        elif event.direction == gtk.gdk.SCROLL_DOWN:
365
            x = 1.0 / 1.2
366
        self._do_zoom(x)
367

    
368
    def settings(self, menuitem):
369
        Settings(self)
370
        
371
    def scroll(self, window, event):
372
        from copy import copy    
373
        CTRL = event.state == gtk.gdk.CONTROL_MASK
374
        SHIFT = event.state == gtk.gdk.SHIFT_MASK
375
        dxdydz = {gtk.keysyms.KP_Add: ('zoom', 1.2, 0),
376
                gtk.keysyms.KP_Subtract: ('zoom', 1 / 1.2, 0),
377
                gtk.keysyms.Up:    ( 0, +1 - CTRL, +CTRL),
378
                gtk.keysyms.Down:  ( 0, -1 + CTRL, -CTRL),
379
                gtk.keysyms.Right: (+1,  0, 0),
380
                gtk.keysyms.Left:  (-1,  0, 0)}.get(event.keyval, None)
381
        try:
382
            inch = chr(event.keyval)
383
        except:
384
            inch = None
385
            
386
        sel = []
387

    
388
        atom_move = self.ui.get_widget('/MenuBar/ToolsMenu/MoveAtoms'
389
                                    ).get_active()
390
        atom_rotate = self.ui.get_widget('/MenuBar/ToolsMenu/RotateAtoms'
391
                                      ).get_active()
392
        atom_orient = self.ui.get_widget('/MenuBar/ToolsMenu/OrientAtoms'
393
                                      ).get_active()
394
        if dxdydz is None:
395
            return
396
        dx, dy, dz = dxdydz
397
        if dx == 'zoom':
398
            self._do_zoom(dy)
399
            return
400
        if dx == 'zoom':
401
            self._do_zoom(dy)
402
            return
403
        d = self.scale * 0.1
404
        tvec = np.array([dx, dy, dz])
405

    
406
        dir_vec = np.dot(self.axes, tvec)
407
        if (atom_move):
408
            rotmat = self.axes
409
            s = 0.1
410
            if SHIFT: 
411
                s = 0.01
412
            add = s * dir_vec
413
            for i in range(len(self.R)):
414
                if self.atoms_to_rotate_0[i]: 
415
                    self.R[i] += add
416
                    for jx in range(self.images.nimages):
417
                        self.images.P[jx][i] += add
418
        elif atom_rotate:
419
            from rot_tools import rotate_about_vec, \
420
                                  rotate_vec
421
            sel = self.images.selected
422
            if sum(sel) == 0:
423
                sel = self.atoms_to_rotate_0
424
            nsel = sum(sel)
425
            # this is the first one to get instatiated
426
            if nsel != 2: 
427
                self.rot_vec = dir_vec
428
                
429
            change = False
430
            z_axis = np.dot(self.axes, np.array([0, 0, 1]))
431
            if self.atoms_to_rotate == None:
432
                change = True 
433
                self.z_axis_old = z_axis.copy()
434
                self.dx_change = [0, 0]
435
                self.atoms_to_rotate = self.atoms_to_rotate_0.copy()
436
                self.atoms_selected = sel.copy()
437
                self.rot_vec = dir_vec
438
                    
439
            if nsel != 2 or sum(self.atoms_to_rotate) == 2:
440
                self.dx_change = [0, 0]
441
                
442
            for i in range(len(sel)):
443
                if sel[i] != self.atoms_selected[i]: 
444
                    change = True
445
            cz = [dx, dy+dz]      
446
            
447
            if cz[0] or cz[1]: 
448
                change = False
449
            if not(cz[0] * (self.dx_change[1])):
450
                change = True 
451
            for i in range(2):
452
                if cz[i] and self.dx_change[i]:
453
                    self.rot_vec = self.rot_vec * cz[i] * self.dx_change[i]
454
                    if cz[1]:
455
                        change = False
456
                
457
            if np.prod(self.z_axis_old != z_axis):
458
                change = True
459
            self.z_axis_old = z_axis.copy()           
460
            self.dx_change = copy(cz)
461
            dihedral_rotation = len(self.images.selected_ordered) == 4
462

    
463
            if change:
464
                self.atoms_selected = sel.copy()
465

    
466
                if nsel == 2 and sum(self.atoms_to_rotate) != 2:
467
                    asel = []
468
                    for i, j in enumerate(sel):
469
                        if j: 
470
                            asel.append(i)
471
                    a1, a2 = asel
472

    
473
                    rvx = self.images.P[self.frame][a1] - \
474
                          self.images.P[self.frame][a2]
475
                        
476
                    rvy = np.cross(rvx,
477
                                   np.dot(self.axes,
478
                                   np.array([0, 0, 1])))     
479
                    self.rot_vec = rvx * dx + rvy * (dy + dz)
480
                    self.dx_change = [dx, dy+dz]
481
                    
482
                # dihedral rotation?
483
                if dihedral_rotation:
484
                    sel = self.images.selected_ordered
485
                    self.rot_vec = (dx+dy+dz)*(self.R[sel[2]]-self.R[sel[1]])
486

    
487
            rot_cen = np.array([0.0, 0.0, 0.0])
488
            if dihedral_rotation:
489
                sel = self.images.selected_ordered
490
                rot_cen = self.R[sel[1]].copy()
491
            elif nsel: 
492
                for i, b in enumerate( sel):
493
                    if b: 
494
                        rot_cen += self.R[i]
495
                rot_cen /= float(nsel)     
496

    
497
            degrees = 5 * (1 - SHIFT) + SHIFT
498
            degrees = abs(sum(dxdydz)) * 3.1415 / 360.0 * degrees
499
            rotmat = rotate_about_vec(self.rot_vec, degrees)
500
            
501
            # now rotate the atoms that are to be rotated            
502
            for i in range(len(self.R)):
503
                if self.atoms_to_rotate[i]: 
504
                    self.R[i] -= rot_cen
505
                    for jx in range(self.images.nimages):
506
                        self.images.P[jx][i] -= rot_cen
507
                    
508
                    self.R[i] = rotate_vec(rotmat, self.R[i])
509
                    for jx in range(self.images.nimages):
510
                        self.images.P[jx][i] = rotate_vec(rotmat, self.images.P[jx][i])
511
                    
512
                    self.R[i] += rot_cen
513
                    for jx in range(self.images.nimages):
514
                        self.images.P[jx][i] += rot_cen
515
        elif atom_orient:
516
            to_vec  = np.array([dx, dy, dz])
517

    
518
            from rot_tools import rotate_vec_into_newvec
519
            rot_mat = rotate_vec_into_newvec(self.orient_normal, to_vec)
520
            self.axes = rot_mat
521
            
522
            self.set_coordinates()
523
        else:
524
            self.center -= (dx * 0.1 * self.axes[:, 0] -
525
                            dy * 0.1 * self.axes[:, 1])
526
        self.draw()
527
    
528
        
529
    def add_atoms(self, widget, data=None):
530
        """
531
        Presents a dialogbox to the user, that allows him to add atoms/molecule to the current slab.
532
        
533
        The molecule/atom is rotated using the current rotation of the coordinate system.
534
        
535
        The molecule/atom can be added at a specified position - if the keyword auto+Z is used,
536
        the COM of the selected atoms will be used as COM for the moleculed. The COM is furthermore
537
        translated Z ang towards the user.
538
        
539
        If no molecules are selected, the COM of all the atoms will be used for the x-y components of the
540
        active coordinate system, while the z-direction will be chosen from the nearest atom position 
541
        along this direction.
542
        
543
        Note: If this option is used, all frames except the active one are deleted.
544
        """
545
        if data:
546
            if data == 'load':
547
                chooser = gtk.FileChooserDialog(
548
                            _('Open ...'), None, gtk.FILE_CHOOSER_ACTION_OPEN,
549
                            (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
550
                             gtk.STOCK_OPEN, gtk.RESPONSE_OK))
551
                ok = chooser.run()
552
                if ok == gtk.RESPONSE_OK:
553
                    filename = chooser.get_filename()
554
                chooser.destroy()
555
                if not ok:
556
                    return
557

    
558
            if data == 'OK' or data == 'load':
559
                import ase
560
                if data == 'load':
561
                    molecule = filename
562
                else:
563
                    molecule = self.add_entries[1].get_text()
564
                tag = self.add_entries[2].get_text()
565
                mom = self.add_entries[3].get_text()
566
                pos = self.add_entries[4].get_text().lower()
567

    
568
                a = None
569
                try:
570
                    a = ase.Atoms([ase.Atom(molecule)])
571
                except:      
572
                    try:
573
                        a = ase.molecule(molecule)
574
                    except:
575
                        try:
576
                            a = ase.read(molecule, -1)
577
                        except:
578
                            self.add_entries[1].set_text('?' + molecule) 
579
                            return ()
580
                        
581
                directions = np.transpose(self.axes)
582
                if a != None:
583
                    for i in a:
584
                        try: 
585
                            i.set_tag(int(tag))
586
                        except: 
587
                            self.add_entries[2].set_text('?' + tag) 
588
                            return ()
589
                        try: 
590
                            i.magmom = float(mom)
591
                        except: 
592
                            self.add_entries[3].set_text('?' + mom) 
593
                            return ()
594
                  # apply the current rotation matrix to A
595
                    for i in a:
596
                        i.position = np.dot(self.axes, i.position)       
597
                  # find the extent of the molecule in the local coordinate system
598
                    a_cen_pos = np.array([0.0, 0.0, 0.0])
599
                    m_cen_pos = 0.0
600
                    for i in a.positions:
601
                        a_cen_pos[0] += np.dot(directions[0], i)
602
                        a_cen_pos[1] += np.dot(directions[1], i)
603
                        a_cen_pos[2] += np.dot(directions[2], i)
604

    
605
                        m_cen_pos = max(np.dot(-directions[2], i), m_cen_pos)
606
                    a_cen_pos[0] /= len(a.positions)      
607
                    a_cen_pos[1] /= len(a.positions)      
608
                    a_cen_pos[2] /= len(a.positions)
609
                    a_cen_pos[2] -= m_cen_pos
610
          
611
                  # now find the position
612
                    cen_pos = np.array([0.0, 0.0, 0.0])
613
                    if sum(self.images.selected) > 0:
614
                        for i in range(len(self.R)):
615
                            if self.images.selected[i]:
616
                                cen_pos += self.R[i]
617
                        cen_pos /= sum(self.images.selected)   
618
                    elif len(self.R) > 0:
619
                        px = 0.0
620
                        py = 0.0
621
                        pz = -1e6
622

    
623
                        for i in range(len(self.R)):
624
                            px += np.dot(directions[0], self.R[i])
625
                            py += np.dot(directions[1], self.R[i])
626
                            pz = max(np.dot(directions[2], self.R[i]), pz)
627
                        px = (px/float(len(self.R)))
628
                        py = (py/float(len(self.R)))
629
                        cen_pos = directions[0] * px + \
630
                                  directions[1] * py + \
631
                                  directions[2] * pz
632
              
633
                    if 'auto' in pos:
634
                        pos = pos.replace('auto', '')
635
                        import re
636
                        pos = re.sub('\s', '', pos)
637
                        if '(' in pos:
638
                            sign = eval('%s1' % pos[0])
639
                            a_cen_pos -= sign * np.array(eval(pos[1:]), float)
640
                        else:
641
                            a_cen_pos -= float(pos) * directions[2]
642
                    else:
643
                        cen_pos = np.array(eval(pos))
644
                    for i in a:
645
                        i.position += cen_pos - a_cen_pos      
646
  
647
                  # and them to the molecule
648
                    atoms = self.images.get_atoms(self.frame)
649
                    atoms = atoms + a
650
                    self.new_atoms(atoms, init_magmom=True)
651
                    
652
                  # and finally select the new molecule for easy moving and rotation
653
                    for i in range(len(a)):
654
                        self.images.selected[len(atoms) - i - 1] = True
655

    
656
                    self.draw()    
657
            self.add_entries[0].destroy()
658
        if data == None:
659
            molecule = ''
660
            tag = '0'
661
            mom = '0'
662
            pos = 'auto+1'
663
            self.add_entries = []
664
            window = gtk.Window(gtk.WINDOW_TOPLEVEL)
665
            self.add_entries.append(window)
666
            window.set_title('Add atoms')
667

    
668
            vbox = gtk.VBox(False, 0)
669
            window.add(vbox)
670
            vbox.show()
671
            pack = False
672
            for i, j in [['Insert atom or molecule', molecule],
673
                         ['Tag', tag],
674
                         ['Moment', mom],
675
                         ['Position', pos]]:
676

    
677
                label = gtk.Label(i)
678
                if not pack:
679
                    vbox.pack_start(label, True, True, 0)
680
                else: 
681
                    pack = True
682
                    vbox.add(label)    
683
                label.show()
684
  
685
                entry = gtk.Entry()
686
                entry.set_text(j)
687
                self.add_entries.append(entry)
688
                entry.set_max_length(50)
689
                entry.show()
690
    
691
                vbox.add(entry)
692

    
693
            button = gtk.Button('_Load molecule')
694
            button.connect('clicked', self.add_atoms, 'load')
695
            button.show()
696
            vbox.add(button)
697
            button = gtk.Button('_OK')
698
            button.connect('clicked', self.add_atoms, 'OK')
699
            button.show()
700
            vbox.add(button)
701
            button = gtk.Button('_Cancel')
702
            button.connect('clicked', self.add_atoms, 'Cancel')
703
            button.show()
704
            vbox.add(button)
705
 
706
            window.show()
707
        
708
    def modify_atoms(self, widget, data=None):
709
        """
710
        Presents a dialog box where the user is able to change the atomic type, the magnetic
711
        moment and tags of the selected atoms. An item marked with X will not be changed.
712
        """
713
        if data:
714
            if data == 'OK':
715
                import ase
716
                symbol = self.add_entries[1].get_text()
717
                tag = self.add_entries[2].get_text()
718
                mom = self.add_entries[3].get_text()
719
                a = None
720
                if symbol != 'X': 
721
                    try:
722
                        a = ase.Atoms([ase.Atom(symbol)])  
723
                    except:
724
                        self.add_entries[1].set_text('?' + symbol)
725
                        return ()
726
          
727
                y = self.images.selected.copy()
728
                    # and them to the molecule
729
                atoms = self.images.get_atoms(self.frame)
730
                for i in range(len(atoms)):
731
                    if self.images.selected[i]:  
732
                        if a: 
733
                            atoms[i].symbol = symbol
734
                        try: 
735
                            if tag != 'X': 
736
                                atoms[i].tag = int(tag)
737
                        except:
738
                            self.add_entries[2].set_text('?' + tag)
739
                            return ()
740
                        try: 
741
                            if mom != 'X': 
742
                                atoms[i].magmom = float(mom)
743
                        except: 
744
                            self.add_entries[3].set_text('?' + mom)
745
                            return ()
746
                self.new_atoms(atoms, init_magmom=True)
747

    
748
                # and finally select the new molecule for easy moving and rotation
749
                self.images.selected = y
750
                self.draw()    
751
          
752
            self.add_entries[0].destroy()          
753
        if data == None and sum(self.images.selected):
754
            atoms = self.images.get_atoms(self.frame)
755
            s_tag = ''
756
            s_mom = ''  
757
            s_symbol = ''
758
          # Get the tags, moments and symbols of the selected atomsa  
759
            for i in range(len(atoms)):
760
                if self.images.selected[i]:
761
                    if not(s_tag): 
762
                        s_tag = str(atoms[i].tag)
763
                    elif s_tag != str(atoms[i].tag): 
764
                        s_tag = 'X'
765
                    if not(s_mom): 
766
                        s_mom = ("%2.2f" % (atoms[i].magmom))
767
                    elif s_mom != ("%2.2f" % (atoms[i].magmom)): 
768
                        s_mom = 'X'
769
                    if not(s_symbol): 
770
                        s_symbol = str(atoms[i].symbol)       
771
                    elif s_symbol != str(atoms[i].symbol): 
772
                        s_symbol = 'X'
773
  
774
            self.add_entries = []
775
            window = gtk.Window(gtk.WINDOW_TOPLEVEL)
776
            self.add_entries.append(window)
777
            window.set_title('Modify')
778

    
779
            vbox = gtk.VBox(False, 0)
780
            window.add(vbox)
781
            vbox.show()
782
            pack = False
783
            for i, j in [['Atom', s_symbol],
784
                        ['Tag', s_tag],
785
                        ['Moment', s_mom]]:
786
                label = gtk.Label(i)
787
                if not pack:
788
                    vbox.pack_start(label, True, True, 0)
789
                else: 
790
                    pack = True
791
                    vbox.add(label)    
792
                label.show()
793

    
794
                entry = gtk.Entry()
795
                entry.set_text(j)
796
                self.add_entries.append(entry)
797
                entry.set_max_length(50)
798
                entry.show()
799
                vbox.add(entry)
800
            button = gtk.Button('_OK')
801
            button.connect('clicked', self.modify_atoms, 'OK')
802
            button.show()
803
            vbox.add(button)
804
            button = gtk.Button('_Cancel')
805
            button.connect('clicked', self.modify_atoms, 'Cancel')
806
            button.show()
807
            vbox.add(button)
808
 
809
            window.show()
810
        
811
    def delete_selected_atoms(self, widget, data=None):
812
        if data == 'OK':
813
            atoms = self.images.get_atoms(self.frame)
814
            lena = len(atoms)
815
            for i in range(len(atoms)):
816
                li = lena-1-i
817
                if self.images.selected[li]:
818
                    del(atoms[li])
819
            self.new_atoms(atoms)
820
         
821
            self.draw()    
822
        if data:      
823
            self.window.destroy()
824
             
825
        if not(data) and sum(self.images.selected):
826
            more = '' + (sum(self.images.selected) > 1) * 's'
827
            self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
828
            self.window.set_title('Confirmation')
829
            self.window.set_border_width(10)
830
            self.box1 = gtk.HBox(False, 0)
831
            self.window.add(self.box1)
832
            self.button1 = gtk.Button('Delete selected atom%s?' % more)
833
            self.button1.connect("clicked", self.delete_selected_atoms, "OK")
834
            self.box1.pack_start(self.button1, True, True, 0)
835
            self.button1.show()
836

    
837
            self.button2 = gtk.Button("Cancel")
838
            self.button2.connect("clicked", self.delete_selected_atoms, "Cancel")
839
            self.box1.pack_start(self.button2, True, True, 0)
840
            self.button2.show()
841

    
842
            self.box1.show()
843
            self.window.show()
844
                
845
    def debug(self, x):
846
        from ase.gui.debug import Debug
847
        Debug(self)
848

    
849
    def execute(self, widget=None):
850
        from ase.gui.execute import Execute
851
        Execute(self)
852
        
853
    def constraints_window(self, widget=None):
854
        from ase.gui.constraints import Constraints
855
        Constraints(self)
856

    
857
    def dft_window(self, widget=None):
858
        from ase.gui.dft import DFT
859
        DFT(self)
860

    
861
    def select_all(self, widget):
862
        self.images.selected[:] = True
863
        self.draw()
864
        
865
    def invert_selection(self, widget):
866
        self.images.selected[:] = ~self.images.selected
867
        self.draw()
868
        
869
    def select_constrained_atoms(self, widget):
870
        self.images.selected[:] = ~self.images.dynamic
871
        self.draw()
872
        
873
    def movie(self, widget=None):
874
        from ase.gui.movie import Movie
875
        self.movie_window = Movie(self)
876
        
877
    def plot_graphs(self, x=None, expr=None):
878
        from ase.gui.graphs import Graphs
879
        g = Graphs(self)
880
        if expr is not None:
881
            g.plot(expr=expr)
882
        self.graph_wref.append(weakref.ref(g))
883

    
884
    def plot_graphs_newatoms(self):
885
        "Notify any Graph objects that they should make new plots."
886
        new_wref = []
887
        found = 0
888
        for wref in self.graph_wref:
889
            ref = wref()
890
            if ref is not None:
891
                ref.plot()
892
                new_wref.append(wref)  # Preserve weakrefs that still work.
893
                found += 1
894
        self.graph_wref = new_wref
895
        return found
896
        
897
    def NEB(self, action):
898
        from ase.gui.neb import NudgedElasticBand
899
        NudgedElasticBand(self.images)
900
        
901
    def bulk_modulus(self, action):
902
        from ase.gui.bulk_modulus import BulkModulus
903
        BulkModulus(self.images)
904
        
905
    def open(self, button=None, filenames=None):
906
        if filenames == None:
907
            chooser = gtk.FileChooserDialog(
908
                _('Open ...'), None, gtk.FILE_CHOOSER_ACTION_OPEN,
909
                (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
910
                 gtk.STOCK_OPEN, gtk.RESPONSE_OK))
911
            ok = chooser.run() == gtk.RESPONSE_OK
912
            if ok:
913
                filenames = [chooser.get_filename()]
914
            chooser.destroy()
915

    
916
            if not ok:
917
                return
918
            
919
        self.reset_tools_modes()     
920
        self.images.read(filenames, slice(None))
921
        self.set_colors()
922
        self.set_coordinates(self.images.nimages - 1, focus=True)
923

    
924
    def import_atoms (self, button=None, filenames=None):
925
        if filenames == None:
926
            chooser = gtk.FileChooserDialog(
927
                _('Open ...'), None, gtk.FILE_CHOOSER_ACTION_OPEN,
928
                (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
929
                 gtk.STOCK_OPEN, gtk.RESPONSE_OK))
930
            ok = chooser.run()
931
            if ok == gtk.RESPONSE_OK:
932
                filenames = [chooser.get_filename()]
933
            chooser.destroy()
934

    
935
            if not ok:
936
                return
937
            
938
            
939
        self.images.import_atoms(filenames, self.frame)
940
        self.set_colors()
941
        self.set_coordinates(self.images.nimages - 1, focus=True)
942

    
943
    def save(self, action):
944
        chooser = gtk.FileChooserDialog(
945
            _('Save ...'), None, gtk.FILE_CHOOSER_ACTION_SAVE,
946
            (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
947
             gtk.STOCK_SAVE, gtk.RESPONSE_OK))
948

    
949
        # Add a file type filter
950
        types = []
951
        name_to_suffix = {}
952
        for name, suffix in [('Automatic', None),
953
                             ('XYZ file', 'xyz'),
954
                             ('ASE trajectory', 'traj'),
955
                             ('PDB file', 'pdb'),
956
                             ('Gaussian cube file', 'cube'),
957
                             ('Python script', 'py'),
958
                             ('VNL file', 'vnl'),
959
                             ('Portable Network Graphics', 'png'),
960
                             ('Persistance of Vision', 'pov'),
961
                             ('Encapsulated PostScript', 'eps'),
962
                             ('FHI-aims geometry input', 'in'),
963
                             ('VASP geometry input', 'POSCAR')]:
964
            if suffix is None:
965
                name = _(name)
966
            else:
967
                name = '%s (%s)' % (_(name), suffix)
968
            filt = gtk.FileFilter()
969
            filt.set_name(name)
970
            if suffix is None:
971
                filt.add_pattern('*')
972
            elif suffix == 'POSCAR':
973
                filt.add_pattern('*POSCAR*')
974
            else:
975
                filt.add_pattern('*.'+suffix)
976
            if suffix is not None:
977
                types.append(suffix)
978
                name_to_suffix[name] = suffix
979
                
980
            chooser.add_filter(filt)
981

    
982
        if self.images.nimages > 1:
983
            img_vbox = gtk.VBox()
984
            button = gtk.CheckButton('Save current image only (#%d)' %
985
                                     self.frame)
986
            pack(img_vbox, button)
987
            entry = gtk.Entry(10)
988
            pack(img_vbox, [gtk.Label(_('Slice: ')), entry,
989
                                        help('Help for slice ...')])
990
            entry.set_text('0:%d' % self.images.nimages)
991
            img_vbox.show()
992
            chooser.set_extra_widget(img_vbox)
993

    
994
        while True:
995
            # Loop until the user selects a proper file name, or cancels.
996
            response = chooser.run()
997
            if response == gtk.RESPONSE_CANCEL:
998
                chooser.destroy()
999
                return
1000
            elif response != gtk.RESPONSE_OK:
1001
                print >>sys.stderr, "AG INTERNAL ERROR: strange response in Save,", response
1002
                chooser.destroy()
1003
                return
1004
                
1005
            filename = chooser.get_filename()
1006

    
1007
            suffix = os.path.splitext(filename)[1][1:]
1008
            if 'POSCAR' in filename:
1009
                suffix = 'POSCAR'
1010
            if suffix == '':
1011
                # No suffix given.  If the user has chosen a special
1012
                # file type, use the corresponding suffix.
1013
                filt = chooser.get_filter().get_name()
1014
                suffix = name_to_suffix[filt]
1015
                filename = filename + '.' + suffix
1016
                
1017
            if suffix not in types:
1018
                oops(message='Unknown output format!',
1019
                     message2='Use one of: ' + ', '.join(types[1:]))
1020
                continue
1021
                
1022
            if self.images.nimages > 1:
1023
                if button.get_active():
1024
                    filename += '@%d' % self.frame
1025
                else:
1026
                    filename += '@' + entry.get_text()
1027

    
1028
            break
1029
        
1030
        chooser.destroy()
1031

    
1032
        bbox = np.empty(4)
1033
        size = np.array([self.width, self.height]) / self.scale
1034
        bbox[0:2] = np.dot(self.center, self.axes[:, :2]) - size / 2
1035
        bbox[2:] = bbox[:2] + size
1036
        suc = self.ui.get_widget('/MenuBar/ViewMenu/ShowUnitCell').get_active()
1037
        self.images.write(filename, self.axes, show_unit_cell=suc, bbox=bbox)
1038
        
1039
    def bulk_window(self, menuitem):
1040
        SetupBulkCrystal(self)
1041

    
1042
    def surface_window(self, menuitem):
1043
        SetupSurfaceSlab(self)
1044

    
1045
    def nanoparticle_window(self, menuitem):
1046
        SetupNanoparticle(self)
1047
        
1048
    def graphene_window(self, menuitem):
1049
        SetupGraphene(self)
1050

    
1051
    def nanotube_window(self, menuitem):
1052
        SetupNanotube(self)
1053

    
1054
    def calculator_window(self, menuitem):
1055
        SetCalculator(self)
1056
        
1057
    def energy_window(self, menuitem):
1058
        EnergyForces(self)
1059
        
1060
    def energy_minimize_window(self, menuitem):
1061
        Minimize(self)
1062

    
1063
    def scaling_window(self, menuitem):
1064
        HomogeneousDeformation(self)
1065
        
1066
    def new_atoms(self, atoms, init_magmom=False):
1067
        "Set a new atoms object."
1068
        self.notify_vulnerable()
1069
        self.reset_tools_modes()
1070
        
1071
        rpt = getattr(self.images, 'repeat', None)
1072
        self.images.repeat_images(np.ones(3, int))
1073
        self.images.initialize([atoms], init_magmom=init_magmom)
1074
        self.frame = 0   # Prevent crashes
1075
        self.images.repeat_images(rpt)
1076
        self.set_colors()
1077
        self.set_coordinates(frame=0, focus=True)
1078

    
1079
    def prepare_new_atoms(self):
1080
        "Marks that the next call to append_atoms should clear the images."
1081
        self.images.prepare_new_atoms()
1082
        
1083
    def append_atoms(self, atoms):
1084
        "Set a new atoms object."
1085
        #self.notify_vulnerable()   # Do this manually after last frame.
1086
        frame = self.images.append_atoms(atoms)
1087
        self.set_coordinates(frame=frame-1, focus=True)
1088

    
1089
    def notify_vulnerable(self):
1090
        """Notify windows that would break when new_atoms is called.
1091

1092
        The notified windows may adapt to the new atoms.  If that is not
1093
        possible, they should delete themselves.
1094
        """
1095
        new_vul = []  # Keep weakrefs to objects that still exist.
1096
        for wref in self.vulnerable_windows:
1097
            ref = wref()
1098
            if ref is not None:
1099
                new_vul.append(wref)
1100
                ref.notify_atoms_changed()
1101
        self.vulnerable_windows = new_vul
1102

    
1103
    def register_vulnerable(self, obj):
1104
        """Register windows that are vulnerable to changing the images.
1105

1106
        Some windows will break if the atoms (and in particular the
1107
        number of images) are changed.  They can register themselves
1108
        and be closed when that happens.
1109
        """
1110
        self.vulnerable_windows.append(weakref.ref(obj))
1111

    
1112
    def exit(self, button, event=None):
1113
        gtk.main_quit()
1114
        return True
1115

    
1116
    def xxx(self, x=None,
1117
            message1='Not implemented!',
1118
            message2='do you really need it?'):
1119
        oops(message1, message2)
1120
        
1121
    def about(self, action):
1122
        try:
1123
            dialog = gtk.AboutDialog()
1124
        except AttributeError:
1125
            self.xxx()
1126
        else:
1127
            dialog.run()
1128

    
1129
def webpage(widget):
1130
    import webbrowser
1131
    webbrowser.open('https://wiki.fysik.dtu.dk/ase/ase/gui.html')
1132