aims_project 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,65 @@
1
+ module AimsProject
2
+
3
+ class CalculationTree < Wx::ScrolledWindow
4
+
5
+ include Wx
6
+
7
+ attr_accessor :app, :treeControl
8
+
9
+ def initialize(app, window)
10
+ super(window)
11
+ self.app = app
12
+
13
+ init_tree
14
+
15
+ sizer = BoxSizer.new(VERTICAL)
16
+ sizer.add(self.treeControl, 1, EXPAND | ALL, 5)
17
+
18
+ set_auto_layout(true)
19
+ set_sizer(sizer)
20
+ end
21
+
22
+ def init_tree
23
+ @treeControl = Wx::TreeCtrl.new(self)
24
+ root = self.treeControl.add_root("-")
25
+ end
26
+
27
+ def show_calculation(calc)
28
+ @tree_map = {}
29
+
30
+ @treeControl.delete_all_items
31
+ root = self.treeControl.add_root(calc.name)
32
+ input_geom = @treeControl.append_item(root, calc.geometry)
33
+ @tree_map[input_geom] = calc.input_geometry
34
+ @treeControl.append_item(root, calc.control)
35
+ @treeControl.append_item(root, calc.status)
36
+ @treeControl.append_item(root, "CONVERGED: #{calc.converged?}")
37
+ # @treeControl.append_item(root, calc.output.total_wall_time)
38
+ if calc.output
39
+ calc.output.geometry_steps.each{|step|
40
+ step_id = @treeControl.append_item(root, "Step %i" % step.step_num)
41
+ @tree_map[step_id] = step
42
+ @treeControl.append_item(step_id, "Total Energy: %f" % step.total_energy)
43
+ @treeControl.append_item(step_id, "SC Iters: %i" % step.sc_iterations.size)
44
+ @treeControl.append_item(step_id, "Wall Time: %f" % step.total_wall_time.to_s)
45
+ }
46
+ end
47
+ @treeControl.expand(root)
48
+ # self.app.project.calculations.each{|calc|
49
+ # calcid = self.treeControl.append_item(root, calc.name)
50
+ # @tree_map[calcid] = calc
51
+ # }
52
+
53
+ evt_tree_sel_changed(self.treeControl) {|evt|
54
+ item = @tree_map[evt.get_item]
55
+ if item.is_a? Aims::GeometryStep
56
+ self.app.show_geometry(item.geometry)
57
+ end
58
+ if item.is_a? Aims::Geometry
59
+ self.app.show_geometry(item)
60
+ end
61
+ }
62
+ end
63
+
64
+ end
65
+ end
@@ -0,0 +1,141 @@
1
+ module AimsProject
2
+
3
+ class CalculationWindow < Wx::Panel
4
+
5
+ include Wx
6
+
7
+ CALC_TABLE_COLS=4
8
+
9
+ def initialize(app, parent)
10
+
11
+ super(parent)
12
+ @app = app
13
+
14
+ # Initialize the selection
15
+ @selection = {}
16
+
17
+ # The inspector window
18
+ @inspector_window = @app.inspector.add_inspector_window
19
+
20
+ # Initialize the options for the crystal viewer
21
+ @options = CrystalViewerOptions.new(@inspector_window)
22
+
23
+ # Top level is a splitter
24
+ topSplitterWindow = SplitterWindow.new(self)
25
+ sizer = VBoxSizer.new
26
+ sizer.add_item(topSplitterWindow, :proportion => 1, :flag => EXPAND)
27
+
28
+ set_sizer(sizer)
29
+
30
+ # The top is a list control
31
+ @calcTable = Grid.new(topSplitterWindow, -1)
32
+ init_table
33
+
34
+ # Populate the calculations list
35
+ @calcs = @app.project.calculations.sort{|a,b| a.name <=> b.name}
36
+ @calcs.each_with_index{|calc, i|
37
+ add_calc_at_row(calc, i)
38
+ }
39
+ @calcTable.auto_size
40
+
41
+ # The bottom is a vertical splitter
42
+ calcWindowSplitter = SplitterWindow.new(topSplitterWindow)
43
+
44
+ # with a tree and a viewer
45
+ @calcTree = CalculationTree.new(self, calcWindowSplitter)
46
+ @calcViewer = CrystalViewer.new(self, calcWindowSplitter, @options)
47
+ calcWindowSplitter.split_vertically(@calcTree, @calcViewer)
48
+
49
+
50
+ # Split the top and bottom
51
+ topSplitterWindow.split_horizontally(@calcTable, calcWindowSplitter, 100)
52
+
53
+ # Setup the events
54
+ evt_grid_cmd_range_select(@calcTable) {|evt|
55
+ if evt.selecting
56
+ row = evt.get_top_row
57
+ puts "CalculationWindow.show_calculation #{@calcs[row].calculation_directory}"
58
+ show_calculation(@calcs[row])
59
+ end
60
+ }
61
+
62
+ evt_thread_callback {|evt|
63
+ @calcTree.show_calculation(@calculation)
64
+ if @calculation.final_geometry
65
+ show_geometry(@calculation.final_geometry)
66
+ else
67
+ show_geometry(@calculation.input_geometry)
68
+ end
69
+ }
70
+
71
+ end
72
+
73
+ def init_table
74
+ @calcTable.create_grid(@app.project.calculations.size, CALC_TABLE_COLS, Grid::GridSelectRows)
75
+ @calcTable.set_col_label_value(0, "Geometry")
76
+ @calcTable.set_col_label_value(1, "Subdirectory")
77
+ @calcTable.set_col_label_value(2, "Control")
78
+ @calcTable.set_col_label_value(3, "Status")
79
+
80
+ end
81
+
82
+ # Insert a calculation in the table at the specified row
83
+ def add_calc_at_row(calc, row)
84
+ @calcTable.set_cell_value(row, 0, calc.geometry)
85
+ @calcTable.set_cell_value(row, 1, calc.calc_subdir.to_s)
86
+ @calcTable.set_cell_value(row, 2, calc.control)
87
+ @calcTable.set_cell_value(row, 3, calc.status)
88
+ end
89
+
90
+ def show_inspector
91
+ @app.inspector.show_inspector_window(@inspector_window)
92
+ end
93
+
94
+ def show_calculation(calc)
95
+ begin
96
+ @calculation = calc
97
+ @err = nil
98
+ t = Thread.new(self) { |evtHandler|
99
+ begin
100
+ @app.set_status("Loading #{@calculation.name}")
101
+ @calculation.load_output
102
+ evt = ThreadCallbackEvent.new
103
+ evtHandler.add_pending_event(evt)
104
+ @app.set_status("")
105
+ rescue $! => e
106
+ @app.set_status(e.message)
107
+ end
108
+ }
109
+ t.priority = t.priority + 100
110
+ rescue $! => e
111
+ puts e.message
112
+ puts e.backtrace
113
+ @app.error_dialog(e)
114
+ end
115
+ end
116
+
117
+ # Get an Image
118
+ def image
119
+ @calcViewer.image
120
+ end
121
+
122
+ # get the currently displayed geometry
123
+ def geometry
124
+ @calcViewer.unit_cell
125
+ end
126
+
127
+ # Display the given geometry
128
+ def show_geometry(geometry)
129
+ @calcViewer.unit_cell = GeometryFile.new(geometry)
130
+ end
131
+
132
+ def select_atom(atom)
133
+ @app.set_status(atom.format_geometry_in)
134
+ end
135
+
136
+ def nudge_selected_atoms(x,y,z)
137
+ @app.error_dialog("Sorry, 'nudge' doesn't work on calculation outputs.")
138
+ end
139
+
140
+ end
141
+ end
@@ -0,0 +1,981 @@
1
+ module AimsProject
2
+ class CrystalViewer < Wx::Panel
3
+
4
+ include Wx
5
+ include Math
6
+ include Gl
7
+ include Glu
8
+ include Aims
9
+
10
+ ID_ROTATE = 100
11
+ ID_PAN = 101
12
+ ID_ZOOM = 102
13
+
14
+ PICK_ID_ATOM = 0x10000000
15
+ PICK_ID_PLANE = 0x20000000
16
+ PICK_ID_BOND = 0x30000000
17
+
18
+ # An array of Aims::UnitCell's to display
19
+ attr_reader :unit_cell
20
+ attr_reader :unit_cell_corrected
21
+
22
+ # How many times to repeat the unit cell
23
+ # A three element vector
24
+ attr_accessor :repeat
25
+
26
+ # If displaying multiple, then the current cell
27
+ attr_accessor :current_cell
28
+
29
+ # The keyboard actions
30
+ attr_accessor :mouse_motion_func # a string "rotate", "pan" or "zoom"
31
+
32
+ # The background color (glClearColor)
33
+ attr_accessor :background
34
+
35
+ attr_accessor :ortho_side, :ortho_zmin, :ortho_zmax
36
+ attr_accessor :x_down, :y_down, :alt, :az, :offx, :offy, :offz
37
+ attr_accessor :orthographic, :width, :height, :picking, :atom, :x_last, :y_last, :z_last
38
+ attr_accessor :render_mode
39
+
40
+ attr_accessor :xmax_plane, :xmin_plane
41
+ attr_accessor :ymax_plane, :ymin_plane
42
+ attr_accessor :zmax_plane, :zmin_plane
43
+
44
+ attr_accessor :slices, :stacks
45
+
46
+ attr_accessor :atoms_changed
47
+
48
+ attr_accessor :selection
49
+
50
+ def initialize(controller, parent, options = nil)
51
+
52
+ super(parent)
53
+ @controller = controller
54
+
55
+ # Register self as an observer of the options
56
+ if options
57
+ @options = options
58
+ else
59
+ @options = CrystalViewerOptions.new(parent)
60
+ end
61
+ @options.add_observer(self)
62
+
63
+ # Toolbar for the GL Canvas
64
+ basedir = File.dirname(__FILE__)
65
+ rotate_icon = Image.new(File.join(basedir,"rotate.gif"), BITMAP_TYPE_GIF)
66
+ @rotate_tool = BitmapButton.new(self, :id => ID_ROTATE, :bitmap => rotate_icon.rescale(16,15).convert_to_bitmap,:name => "rotate")
67
+
68
+ zoom_icon = Image.new(File.join(basedir,"zoom.gif"), BITMAP_TYPE_GIF)
69
+ @zoom_tool = BitmapButton.new(self, :id => ID_ZOOM, :bitmap => zoom_icon.rescale(16,15).convert_to_bitmap, :name => "zoom")
70
+
71
+ pan_icon = Image.new(File.join(basedir,"pan.gif"), BITMAP_TYPE_GIF)
72
+ @pan_tool = BitmapButton.new(self, :id => ID_PAN, :bitmap => pan_icon.rescale(16,15).convert_to_bitmap, :name => "pan")
73
+
74
+ evt_button(@rotate_tool) {|evt|
75
+ set_mouse_motion_function(:rotate)
76
+ }
77
+
78
+ evt_button(@zoom_tool) {|evt|
79
+ set_mouse_motion_function(:zoom)
80
+ }
81
+
82
+ evt_button(@pan_tool) {|evt|
83
+ set_mouse_motion_function(:pan)
84
+ }
85
+
86
+ @buttonSizer = HBoxSizer.new
87
+ @buttonSizer.add_item(@rotate_tool)
88
+ @buttonSizer.add_item(@zoom_tool)
89
+ @buttonSizer.add_item(@pan_tool)
90
+
91
+
92
+ #@glPanel = CalendarCtrl.new(self)
93
+ attrib = [Wx::GL_RGBA, Wx::GL_DOUBLEBUFFER, Wx::GL_DEPTH_SIZE, 24]
94
+ @glPanel = GLCanvas.new(self, -1, [-1, -1], [-1, -1],
95
+ Wx::FULL_REPAINT_ON_RESIZE, "GLCanvas", attrib)
96
+
97
+ vbox_sizer = VBoxSizer.new
98
+ vbox_sizer.add_item(@buttonSizer, :flag => EXPAND)
99
+ vbox_sizer.add_item(@glPanel, :proportion => 1, :flag => EXPAND)
100
+ set_sizer(vbox_sizer)
101
+
102
+
103
+ set_defaults
104
+
105
+ # Define the method to call for paint requests
106
+ evt_paint { @glPanel.paint { draw_scene }}
107
+ # Create the graphics view and define event handlers
108
+
109
+ # For some reason, not all left-clicks are captured,
110
+ # so we include this catchall as well, to prevent odd
111
+ # behavior when rotating
112
+ @glPanel.evt_mouse_events {|evt|
113
+ if evt.button_down
114
+ self.set_focus
115
+ mouse_down(evt.get_x, evt.get_y)
116
+ end
117
+ }
118
+
119
+ @glPanel.evt_left_up {|evt|
120
+ mouse_up(evt.get_x, evt.get_y)
121
+ draw_scene
122
+ }
123
+
124
+ @glPanel.evt_motion {|evt|
125
+ if evt.dragging
126
+ mouse_dragged(evt.get_x, evt.get_y)
127
+ draw_scene
128
+ end
129
+ }
130
+
131
+ @glPanel.evt_char {|evt|
132
+ nudge_dir = case evt.get_key_code
133
+ when K_LEFT
134
+ [-1,0,0]
135
+ when K_RIGHT
136
+ [1,0,0]
137
+ when K_UP
138
+ [0,1,0]
139
+ when K_DOWN
140
+ [0,-1,0]
141
+ else
142
+ [0,0,0]
143
+ end
144
+ nudge(nudge_dir)
145
+ }
146
+
147
+ # What to do when worker threads return
148
+ evt_thread_callback {|evt|
149
+ self.draw_scene
150
+ }
151
+ end
152
+
153
+ # Called when the options changes
154
+ def update
155
+ self.draw_scene
156
+ end
157
+
158
+ def set_defaults
159
+ self.current_cell = 0
160
+ self.background = Material.new(0.7, 0.7, 1.0, 1)
161
+ self.x_down = 0
162
+ self.y_down = 0
163
+ self.x_last = 0
164
+ self.y_last = 0
165
+ self.z_last = 0
166
+ self.alt = 0
167
+ self.az = 0
168
+ self.offx = 0
169
+ self.offy = 0
170
+ self.offz = -20
171
+ self.orthographic = true
172
+ self.ortho_side = 15
173
+ self.ortho_zmin = -1
174
+ self.ortho_zmax = 50
175
+ self.width = 500
176
+ self.height = 500
177
+ self.picking =false
178
+ # The last clicked atom
179
+ self.atom = nil
180
+ self.atoms_changed = true
181
+ self.hiRes
182
+ self.mouse_motion_func = :rotate
183
+ self.repeat = Vector[1,1,1]
184
+ self.render_mode = :ball_stick
185
+ @options.set_properties(
186
+ :bond_length => 3,
187
+ :show_bonds => true,
188
+ :show_lighting => true,
189
+ :show_xclip => false,
190
+ :show_yclip => false,
191
+ :show_zclip => true,
192
+ :show_supercell => true
193
+ )
194
+ end
195
+
196
+
197
+ def nudge(dir)
198
+ @controller.nudge_selected_atoms(dir[0]*0.5, dir[1]*0.5, 0)
199
+ end
200
+
201
+
202
+ =begin
203
+ Set the unit cell to display.
204
+ =end
205
+ def unit_cell=(uc)
206
+
207
+ if @unit_cell
208
+ @unit_cell.delete_observer(self)
209
+ end
210
+
211
+ @unit_cell = uc
212
+ @unit_cell_corrected = nil
213
+
214
+ init_clip_planes
215
+
216
+ @unit_cell.add_observer(self, :unit_cell_changed)
217
+
218
+ end
219
+
220
+ def unit_cell_changed
221
+ init_clip_planes
222
+ draw_scene
223
+ end
224
+
225
+ def init_clip_planes
226
+
227
+ Thread.new(self) { |evtHandler|
228
+ @unit_cell_corrected = @unit_cell.correct
229
+ evt = ThreadCallbackEvent.new
230
+ evtHandler.add_pending_event(evt)
231
+ }
232
+
233
+ # each bounding box is a 2 element array [max, min]
234
+ bounding_box = @unit_cell.bounding_box(false)
235
+ xmax = bounding_box[0].x
236
+ xmin = bounding_box[1].x
237
+ ymax = bounding_box[0].y
238
+ ymin = bounding_box[1].y
239
+ zmax = bounding_box[0].z
240
+ zmin = bounding_box[1].z
241
+
242
+ @xmax_plane = Plane.new( 1, 0, 0, xmax, 0, 0)
243
+ @xmin_plane = Plane.new(-1, 0, 0, xmin, 0, 0)
244
+ @ymax_plane = Plane.new( 0, 1, 0, 0, ymax, 0)
245
+ @ymin_plane = Plane.new( 0,-1, 0, 0, ymin, 0)
246
+ @zmax_plane = Plane.new( 0, 0, 1, 0, 0, zmax)
247
+ @zmin_plane = Plane.new( 0, 0,-1, 0, 0, zmin)
248
+
249
+ # Add clip-planes to each unit cell
250
+ @unit_cell.clear_planes
251
+ @unit_cell.add_plane(@xmax_plane, false)
252
+ @unit_cell.add_plane(@xmin_plane, false)
253
+ @unit_cell.add_plane(@ymax_plane, false)
254
+ @unit_cell.add_plane(@ymin_plane, false)
255
+ @unit_cell.add_plane(@zmax_plane, false)
256
+ @unit_cell.add_plane(@zmin_plane, false)
257
+ @unit_cell.recache_visible_atoms
258
+ @unit_cell.make_bonds(@options.bond_length)
259
+
260
+ end
261
+
262
+ def dump_properties
263
+ self.instance_variables.each{|v|
264
+ puts "#{v} = #{self.instance_variable_get(v)}"
265
+ }
266
+ end
267
+
268
+ # The currently displayed unit cell
269
+ def current_unit_cell
270
+ self.unit_cell
271
+ end
272
+
273
+ def dump_geometry
274
+ atoms = self.unit_cell
275
+ if atoms
276
+ puts atoms.format_geometry_in
277
+ end
278
+ end
279
+
280
+ def bond_length=(l)
281
+ @options.bond_length = l
282
+ # FIXME The bonds should be made in the controller
283
+ # self.unit_cell.each{|uc| uc.make_bonds(l)} if self.unit_cell
284
+ end
285
+
286
+ def mouse_down(x,y)
287
+ self.x_down = x
288
+ self.y_down = y
289
+ self.x_last = x
290
+ self.y_last = y
291
+ self.loRes
292
+ end
293
+
294
+ def mouse_up(x,y)
295
+ return unless self.unit_cell
296
+ @picked = pick_object(x,y)
297
+ self.atom = self.unit_cell.atoms.find{|a| a.id == @picked[:atoms].last}
298
+ if self.atom
299
+ @controller.select_atom(self.atom)
300
+ # puts self.atom.format_geometry_in
301
+ end
302
+
303
+ unless @picked[:planes].empty?
304
+ clip_plane_id = @picked[:planes].first
305
+ @active_clip_plane = case clip_plane_id
306
+ when 1
307
+ @zmax_plane
308
+ when 2
309
+ @zmin_plane
310
+ when 3
311
+ @xmax_plane
312
+ when 4
313
+ @xmin_plane
314
+ when 5
315
+ @ymax_plane
316
+ when 6
317
+ @ymin_plane
318
+ else
319
+ nil
320
+ end
321
+ if @active_clip_plane
322
+ set_mouse_motion_function(:move_clip_plane)
323
+ end
324
+ end
325
+ self.hiRes
326
+
327
+ # Harmless correction for bug in WxRuby that doesn't register all mouse down events
328
+ self.x_last = nil
329
+ self.y_last = nil
330
+
331
+ glutPostRedisplay if @using_glut
332
+ end
333
+
334
+ def mouse_dragged(x, y)
335
+
336
+ # Harmless correction for bug in WxRuby that doesn't register all mouse down events
337
+ self.x_last = x if self.x_last.nil?
338
+ self.y_last = y if self.y_last.nil?
339
+ self.z_last = z if self.z_last.nil?
340
+
341
+ case self.mouse_motion_func
342
+ when :rotate
343
+ rotate(x,y)
344
+ when :zoom
345
+ zoom(x,y)
346
+ when :pan
347
+ pan(x,y)
348
+ when :move_clip_plane
349
+ move_clip_plane(x,y)
350
+ else
351
+ rotate(x,y)
352
+ end
353
+ glutPostRedisplay if @using_glut
354
+ end
355
+
356
+ def delete_atom
357
+ if self.atom
358
+ # Remove
359
+ self.unit_cell.remove_atom(self.atom)
360
+ end
361
+ end
362
+
363
+ # Unproject the mouse coordinates to model space
364
+ # Handles the flipping of the y-coordinate
365
+ def unproject(x,y,z)
366
+ model = glGetDoublev(GL_MODELVIEW_MATRIX)
367
+ proj = glGetDoublev(GL_PROJECTION_MATRIX)
368
+ viewport = glGetIntegerv(GL_VIEWPORT);
369
+
370
+ gluUnProject(x, viewport[3]-y, z, model, proj, viewport)
371
+ end
372
+
373
+ # Cast a ray from the near clip-plane to the far clip-plane through the point x,y
374
+ # return a two element array
375
+ # element 0 is the model space coordinates of (x,y) on the near clip plane
376
+ # element 1 is the direction of the ray
377
+ def cast_ray_to(x,y)
378
+ x1,y1,z1 = unproject(x,y,0)
379
+ x2,y2,z2 = unproject(x,y,1)
380
+ [ Vector[x1, y1, z1], Vector[x2-x1, y2-y1, z2-z1] ]
381
+ end
382
+
383
+ def move_clip_plane(x,y)
384
+
385
+ x_obj_last, y_obj_last, z_obj_last = unproject(x_last, y_last, 0)
386
+ x_obj, y_obj, z_obj = unproject(x,y,0)
387
+
388
+ w = [x_obj - x_obj_last, y_obj - y_obj_last, z_obj - z_obj_last]
389
+ n = @active_clip_plane.unit_normal
390
+ d = w[0]*n[0] + w[1]*n[1] + w[2]*n[2]
391
+
392
+ # scale = 0.1
393
+ # dx = (x - self.x_last)
394
+ # dy = (y - self.y_last)
395
+ # dr = sqrt(dx*dx + dy*dy)*(0 > dy ? -1 : 1)*scale
396
+ self.x_last = x
397
+ self.y_last = y
398
+
399
+ @active_clip_plane.displace_along_normal(d)
400
+ self.atoms_changed = true
401
+ end
402
+
403
+ def rotate(x,y)
404
+ self.az += 5*(x - self.x_last)
405
+ self.alt += 5*(y - self.y_last)
406
+ self.x_last = x
407
+ self.y_last = y
408
+ end
409
+
410
+ def zoom(x,y)
411
+ if self.orthographic
412
+ self.ortho_side -= (y - self.y_last)*0.1
413
+ else
414
+ self.offz -= (y - self.y_last)*0.1
415
+ end
416
+ self.x_last = x
417
+ self.y_last = y
418
+ end
419
+
420
+ def pan(x,y)
421
+
422
+ z = 0.0 # This value is normalized with respect to the near and far clip planes
423
+
424
+ obj_x, obj_y, obj_z = unproject(x, y, z)
425
+ obj_x_last, obj_y_last, obj_z_last = unproject(x_last, y_last, z_last)
426
+ @center.x += obj_x_last - obj_x
427
+ @center.y += obj_y_last - obj_y
428
+ @center.z += obj_z_last - obj_z
429
+
430
+ self.x_last = x
431
+ self.y_last = y
432
+ self.z_last = z
433
+ end
434
+
435
+ def loRes
436
+ self.slices = 5
437
+ self.stacks = 5
438
+ end
439
+
440
+ def hiRes
441
+ self.slices = 20
442
+ self.stacks = 20
443
+ end
444
+
445
+ def set_view(offset_x, offset_y, offset_z, alt, az)
446
+ self.offx = offset_x
447
+ self.offy = offset_y
448
+ self.offz = offset_z
449
+ self.alt = alt
450
+ self.az = az
451
+ end
452
+
453
+ # Executes the named method when the mouse is dragged
454
+ def set_mouse_motion_function(method_name)
455
+ self.mouse_motion_func = method_name.to_sym
456
+ end
457
+
458
+ def draw_scene
459
+
460
+ @glPanel.set_current
461
+ sz = @glPanel.size
462
+ viewport_setup(sz.width, sz.height)
463
+
464
+
465
+ begin
466
+ draw_init
467
+ apply_projection
468
+ position_camera
469
+ add_lights if @options.show_lighting
470
+ outline_supercell if @options.show_supercell
471
+
472
+ if self.unit_cell
473
+ atoms = self.unit_cell
474
+ @options.x_repeat.times do |i|
475
+ @options.y_repeat.times do |j|
476
+ @options.z_repeat.times do |k|
477
+
478
+ origin = atoms.lattice_vectors[0]*i + atoms.lattice_vectors[1]*j + atoms.lattice_vectors[2]*k
479
+
480
+ draw_bonds(origin) if @options.show_bonds
481
+ draw_lattice(origin)
482
+ end
483
+ end
484
+ end
485
+ end
486
+ draw_clip_planes
487
+ rescue AimsProjectException => e
488
+ puts e.message
489
+ puts e.backtrace.join("\n")
490
+ end
491
+
492
+ @glPanel.swap_buffers
493
+ end
494
+
495
+ def viewport_setup(width, height)
496
+ self.width = width
497
+ self.height = height
498
+ glViewport(0, 0, self.width, self.height)
499
+ end
500
+
501
+ def apply_projection
502
+ glMatrixMode(GL_PROJECTION)
503
+ glLoadIdentity()
504
+ if self.orthographic
505
+ glOrtho(-self.ortho_side, self.ortho_side, -self.ortho_side,self.ortho_side, self.ortho_zmin, self.ortho_zmax)
506
+ else
507
+ gluPerspective(60, self.width/self.height, 1, 120)
508
+ end
509
+ glMatrixMode(GL_MODELVIEW)
510
+ end
511
+
512
+ def draw_init
513
+ glClearColor(background.r,background.g,background.b,@options.solid_bg)
514
+ glEnable(GL_DEPTH_TEST)
515
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
516
+
517
+
518
+ # Antialiasing
519
+ glEnable(GL_LINE_SMOOTH)
520
+ glEnable(GL_BLEND)
521
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
522
+ glHint(GL_LINE_SMOOTH_HINT, GL_NICEST)
523
+ glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST)
524
+
525
+ if @options.show_lighting
526
+ glEnable(GL_LIGHTING)
527
+ glEnable(GL_LIGHT0)
528
+ else
529
+ glDisable(GL_LIGHTING)
530
+ end
531
+
532
+ # Point size
533
+ glPointSize(5)
534
+ glLineWidth(2)
535
+ end
536
+
537
+ def pick_object(x,y)
538
+
539
+ buf = glSelectBuffer(512)
540
+ glRenderMode(GL_SELECT)
541
+ model = glGetDoublev(GL_MODELVIEW_MATRIX)
542
+ proj = glGetDoublev(GL_PROJECTION_MATRIX)
543
+ viewport = glGetIntegerv(GL_VIEWPORT);
544
+
545
+ self.picking = true
546
+ glMatrixMode(GL_PROJECTION)
547
+ glLoadIdentity()
548
+ gluPickMatrix(x,viewport[3]-y,5,5,viewport)
549
+ if self.orthographic
550
+ glOrtho(-self.ortho_side, self.ortho_side, -self.ortho_side,self.ortho_side, self.ortho_zmin, self.ortho_zmax)
551
+ else
552
+ gluPerspective(60, self.width/self.height, 1, 60)
553
+ end
554
+
555
+ z = 0.0 # This value is normalized with respect to the near and far clip planes
556
+ obj_x, obj_y, obj_z = gluUnProject(x, viewport[3]-y, z, model, proj, viewport)
557
+
558
+ glMatrixMode(GL_MODELVIEW)
559
+
560
+ glInitNames
561
+ if self.unit_cell
562
+ atoms = self.unit_cell
563
+ @options.x_repeat.times do |i|
564
+ @options.y_repeat.times do |j|
565
+ @options.z_repeat.times do |k|
566
+
567
+ origin = atoms.lattice_vectors[0]*i + atoms.lattice_vectors[1]*j + atoms.lattice_vectors[2]*k
568
+
569
+ self.draw_lattice(origin)
570
+ self.draw_clip_planes
571
+ end
572
+ end
573
+ end
574
+ end
575
+
576
+ self.picking = false
577
+
578
+ glMatrixMode(GL_MODELVIEW);
579
+ glFlush();
580
+
581
+ count = glRenderMode(GL_RENDER)
582
+ data = buf.unpack("L!*")
583
+ names = []
584
+ count.times do
585
+ num_names = data.shift
586
+ min_depth = data.shift
587
+ max_depth = data.shift
588
+ num_names.times do
589
+ names << data.shift
590
+ end
591
+ end
592
+
593
+ picked_objects = {:atoms => [], :planes => [], :bonds => []}
594
+ names.each{|n|
595
+ if (n & PICK_ID_ATOM) == PICK_ID_ATOM
596
+ picked_objects[:atoms] << (n ^ PICK_ID_ATOM)
597
+ end
598
+ if (n & PICK_ID_PLANE) == PICK_ID_PLANE
599
+ picked_objects[:planes] << (n ^ PICK_ID_PLANE)
600
+ end
601
+ }
602
+
603
+ picked_objects
604
+ end
605
+
606
+ def position_camera
607
+ return unless self.unit_cell
608
+ atoms = self.unit_cell
609
+
610
+ # Find the center of all atoms, not just visible ones.
611
+ @center = atoms.center unless @center
612
+
613
+ # Move camera out along z-axis
614
+ glMatrixMode(GL_MODELVIEW)
615
+ glLoadIdentity()
616
+
617
+ glTranslatef(self.offx,self.offy,self.offz)
618
+ glRotatef(self.alt, 1, 0, 0)
619
+ glRotatef(self.az, 0, 0, 1)
620
+ glTranslatef(-@center.x, -@center.y, -@center.z)
621
+ end
622
+
623
+ def add_lights
624
+ light0_position = [1,1,1,0]
625
+ light1_position = [-1,-1,1,1]
626
+ glLightModel(GL_LIGHT_MODEL_AMBIENT, [0.6, 0.6, 0.6 ,1.0])
627
+ # glLightModel(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_TRUE)
628
+ glLightfv(GL_LIGHT0, GL_POSITION, light0_position)
629
+ glLightfv(GL_LIGHT1, GL_POSITION, light1_position)
630
+ end
631
+
632
+ def outline_supercell
633
+ return unless self.unit_cell
634
+ uc = self.unit_cell
635
+
636
+ vecs = uc.lattice_vectors
637
+ return unless vecs
638
+
639
+ origin = [0, 0, 0]
640
+ v1 = vecs[0]
641
+ v2 = vecs[1]
642
+ v3 = vecs[2]
643
+
644
+ # Corner #1
645
+ c1 = v1 + v3
646
+
647
+ # Corner #2
648
+ c2 = v2 + v3
649
+
650
+ # Corner #3
651
+ c3 = v1 + v2
652
+
653
+ # Corner #4
654
+ c4 = v1 + v2 + v3
655
+
656
+ Material.black.apply
657
+ glLineWidth(1.0)
658
+
659
+ glBegin(GL_LINES)
660
+
661
+ glVertex3f(origin[0], origin[1], origin[2])
662
+ glVertex3f(v1[0], v1[1], v1[2])
663
+
664
+ glVertex3f(origin[0], origin[1], origin[2])
665
+ glVertex3f(v2[0], v2[1], v2[2])
666
+
667
+ glVertex3f(origin[0], origin[1], origin[2])
668
+ glVertex3f(v3[0], v3[1], v3[2])
669
+
670
+ glVertex3f(v1[0], v1[1], v1[2])
671
+ glVertex3f(c3[0], c3[1], c3[2])
672
+
673
+ glVertex3f(v2[0], v2[1], v2[2])
674
+ glVertex3f(c3[0], c3[1], c3[2])
675
+
676
+ glVertex3f(c3[0], c3[1], c3[2])
677
+ glVertex3f(c4[0], c4[1], c4[2])
678
+
679
+ glVertex3f(v1[0], v1[1], v1[2])
680
+ glVertex3f(c1[0], c1[1], c1[2])
681
+
682
+ glVertex3f(v2[0], v2[1], v2[2])
683
+ glVertex3f(c2[0], c2[1], c2[2])
684
+
685
+ glVertex3f(c1[0], c1[1], c1[2])
686
+ glVertex3f(c4[0], c4[1], c4[2])
687
+
688
+ glVertex3f(c2[0], c2[1], c2[2])
689
+ glVertex3f(c4[0], c4[1], c4[2])
690
+
691
+ glVertex3f(v3[0], v3[1], v3[2])
692
+ glVertex3f(c1[0], c1[1], c1[2])
693
+
694
+ glVertex3f(v3[0], v3[1], v3[2])
695
+ glVertex3f(c2[0], c2[1], c2[2])
696
+
697
+ glEnd()
698
+ end
699
+
700
+ def draw_bonds(origin = [0,0,0])
701
+ return unless self.unit_cell
702
+ atoms = if @options.correct
703
+ @unit_cell_corrected
704
+ else
705
+ @unit_cell
706
+ end
707
+ return unless atoms.bonds
708
+
709
+ Material.black.apply
710
+ glLineWidth(1.0)
711
+ glBegin(GL_LINES)
712
+ atoms.bonds.each{|b|
713
+ glVertex3f(origin[0] + b[0].x, origin[1] + b[0].y, origin[2] + b[0].z)
714
+ glVertex3f(origin[0] + b[1].x, origin[1] + b[1].y, origin[2] + b[1].z)
715
+ }
716
+ glEnd()
717
+ end
718
+
719
+ #
720
+ # Draw an atom
721
+ # @param x The x-coordinate
722
+ # @param y The y-coordinate
723
+ # @param z The z-coordinate
724
+ # @param r The radius
725
+ # @param name The name of the sphere (for picking)
726
+ # @return a sphere_quadric for reuse if desired. Make sure to delete it when done
727
+ def draw_sphere(x,y,z,r, name, sphere_quadric=nil)
728
+
729
+ unless sphere_quadric
730
+ sphere_quadric = gluNewQuadric()
731
+ gluQuadricDrawStyle(sphere_quadric, GLU_FILL)
732
+ gluQuadricNormals(sphere_quadric, GLU_SMOOTH)
733
+ gluQuadricOrientation(sphere_quadric, GLU_OUTSIDE)
734
+ end
735
+
736
+ # Load a new matrix onto the stack
737
+ glPushMatrix()
738
+ glPushName(name | PICK_ID_ATOM) if self.picking
739
+ glTranslatef(x, y, z)
740
+ gluSphere(sphere_quadric, r, slices, stacks)
741
+ glPopName() if picking
742
+ glPopMatrix()
743
+
744
+ sphere_quadric
745
+ end
746
+
747
+ def draw_lattice(origin = [0,0,0])
748
+
749
+ return unless self.unit_cell
750
+
751
+ atoms = if @options.correct
752
+ @unit_cell_corrected
753
+ else
754
+ @unit_cell
755
+ end
756
+
757
+ return unless atoms
758
+
759
+ # Create sphere object
760
+ rmin = 0.2
761
+ rmax = 0.5
762
+
763
+ if self.atoms_changed
764
+ atoms.recache_visible_atoms
765
+ self.atoms_changed = false
766
+ end
767
+
768
+ # Calculate radius scaling factor vs. depth
769
+ rrange = rmax - rmin
770
+ # bb = atoms.bounding_box
771
+ zmin = atoms.min{|a,b| a.z <=> b.z}.z
772
+ zmax = atoms.max{|a,b| a.z <=> b.z}.z
773
+ zrange = zmax - zmin
774
+ if zrange == 0
775
+ rscale = 0
776
+ else
777
+ rscale = rrange/zrange
778
+ end
779
+
780
+
781
+
782
+ sphere_quadric = nil
783
+ for a in atoms
784
+ a.material.apply(@options.show_lighting)
785
+ case self.render_mode
786
+ when :ball_stick
787
+ r = rmin+(a.z - zmin)*rscale
788
+ else
789
+ r = 2.0
790
+ end
791
+
792
+ sphere_quadric = draw_sphere(origin[0] + a.x, origin[1] + a.y, origin[2] + a.z, r, a.id, sphere_quadric)
793
+
794
+ end
795
+ gluDeleteQuadric(sphere_quadric) if sphere_quadric
796
+
797
+ end
798
+
799
+ # a test method to see of an object of a particular type was picked.
800
+ # valid types are :atom, :plane
801
+ def is_picked?(type, id)
802
+ if @picked
803
+ case type
804
+ when :atom
805
+ @picked[:atoms].member? id
806
+ when :plane
807
+ @picked[:planes].member? id
808
+ when :bond
809
+ @picked[:bonds].member? id
810
+ else
811
+ false
812
+ end
813
+ else
814
+ false
815
+ end
816
+ end
817
+
818
+ #
819
+ # Draw a plane
820
+ # use the vertices defined in lineLoop as the boundary of the plane
821
+ def draw_plane(plane, lineLoop, pickid)
822
+
823
+ glPushName(pickid | PICK_ID_PLANE) if self.picking
824
+
825
+ # if is_picked?(:plane, pickid)
826
+ # Material.black.apply
827
+ # glLineWidth(3.0)
828
+ # glEdgeFlag(GL_TRUE)
829
+ # end
830
+ glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
831
+
832
+ Material.new(0.9, 0.9, 0.0, 0.5).apply
833
+ glBegin(GL_TRIANGLE_FAN)
834
+ glNormal3f(plane.a, plane.b, plane.c)
835
+ lineLoop.each{|p|
836
+ glVertex3f(p[0], p[1], p[2])
837
+ }
838
+ glEnd()
839
+
840
+ glEdgeFlag(GL_FALSE)
841
+ glPopName() if picking
842
+
843
+ if (is_picked?(:plane, pickid))
844
+ Material.black.apply
845
+ glLineWidth(3.0)
846
+ glBegin(GL_LINE_LOOP)
847
+ lineLoop.each{|p|
848
+ glVertex3f(p[0], p[1], p[2])
849
+ }
850
+ glEnd()
851
+ glLineWidth(1.0)
852
+ end
853
+
854
+ end
855
+
856
+ def draw_clip_planes
857
+ return unless self.unit_cell
858
+ atoms = self.unit_cell
859
+
860
+ bb = atoms.bounding_box
861
+ bbx1 = bb[0].x
862
+ bbx2 = bb[1].x
863
+ bby1 = bb[0].y
864
+ bby2 = bb[1].y
865
+ bbz1 = bb[0].z
866
+ bbz2 = bb[1].z
867
+
868
+ # draw z_planes
869
+ # The are bounded by the min and max points in the x-y plane
870
+
871
+ if @options.show_zclip and @zmax_plane and @zmin_plane
872
+ z = -1*@zmax_plane.distance_to_point(0,0,0)
873
+ draw_plane(@zmax_plane, [[bbx1, bby1, z],
874
+ [bbx2, bby1, z],
875
+ [bbx2, bby2, z],
876
+ [bbx1, bby2, z]],1)
877
+
878
+ z = @zmin_plane.distance_to_point(0,0,0)
879
+ draw_plane(@zmin_plane, [[bbx1, bby1, z],
880
+ [bbx2, bby1, z],
881
+ [bbx2, bby2, z],
882
+ [bbx1, bby2, z]],2)
883
+ end
884
+
885
+ if @options.show_xclip and @xmax_plane and @xmin_plane
886
+ x = -1*@xmax_plane.distance_to_point(0,0,0)
887
+ draw_plane(@xmax_plane, [[x, bby1, bbz1],
888
+ [x, bby1, bbz2],
889
+ [x, bby2, bbz2],
890
+ [x, bby2, bbz1]],3)
891
+
892
+ x = @xmin_plane.distance_to_point(0,0,0)
893
+ draw_plane(@xmin_plane, [[x, bby1, bbz1],
894
+ [x, bby1, bbz2],
895
+ [x, bby2, bbz2],
896
+ [x, bby2, bbz1]],4)
897
+ end
898
+
899
+ if @options.show_yclip and @ymax_plane and @ymin_plane
900
+ y = -1*@ymax_plane.distance_to_point(0,0,0)
901
+ draw_plane(@ymax_plane, [[bbx1, y, bbz1],
902
+ [bbx1, y, bbz2],
903
+ [bbx2, y, bbz2],
904
+ [bbx2, y, bbz1]],5)
905
+
906
+ y = @ymin_plane.distance_to_point(0,0,0)
907
+ draw_plane(@ymin_plane, [[bbx1, y, bbz1],
908
+ [bbx1, y, bbz2],
909
+ [bbx2, y, bbz2],
910
+ [bbx2, y, bbz1]],6)
911
+ end
912
+
913
+ end
914
+
915
+ def rgba_image_data
916
+
917
+ # 4 bytes/pixel
918
+ bytewidth = self.width*4
919
+ bytewidth = (bytewidth.to_i + 3) & ~3 # Align to 4 bytes
920
+ bytes = bytewidth * self.height
921
+
922
+ # Finish any pending Commands
923
+ @glPanel.set_current
924
+ glFinish
925
+
926
+ # Setup pixel store
927
+ glPixelStorei(Gl::GL_PACK_ALIGNMENT, 4) # Force 4-byte alignment
928
+ glPixelStorei(Gl::GL_PACK_ROW_LENGTH, 0)
929
+ glPixelStorei(Gl::GL_PACK_SKIP_ROWS, 0)
930
+ glPixelStorei(Gl::GL_PACK_SKIP_PIXELS, 0)
931
+
932
+ glReadPixels(0, 0, self.width, self.height, Gl::GL_RGBA, Gl::GL_UNSIGNED_BYTE)
933
+
934
+ end
935
+
936
+ def image
937
+ image = Image.new(self.width, self.height)
938
+ image.set_rgb_data(self.rgb_image_data)
939
+ image.set_alpha_data(self.alpha_image_data)
940
+ image
941
+ end
942
+
943
+ def rgb_image_data
944
+
945
+ # 3 bytes/pixel
946
+ bytewidth = self.width*3
947
+ bytes = bytewidth * self.height
948
+
949
+ # Finish any pending Commands
950
+ @glPanel.set_current
951
+ glFinish
952
+
953
+ # Setup pixel store
954
+ glPixelStorei(Gl::GL_PACK_ALIGNMENT, 1) # Force byte alignment
955
+ glPixelStorei(Gl::GL_PACK_ROW_LENGTH, 0)
956
+ glPixelStorei(Gl::GL_PACK_SKIP_ROWS, 0)
957
+ glPixelStorei(Gl::GL_PACK_SKIP_PIXELS, 0)
958
+
959
+ glReadPixels(0, 0, self.width, self.height, Gl::GL_RGB, Gl::GL_UNSIGNED_BYTE)
960
+
961
+ end
962
+
963
+ def alpha_image_data
964
+ # 1 byte/pixel
965
+ bytewidth = self.width;
966
+
967
+ bytes = bytewidth*height;
968
+ @glPanel.set_current
969
+ glFinish
970
+
971
+ # Setup pixel store
972
+ glPixelStorei(Gl::GL_PACK_ALIGNMENT, 1) # Force byte alignment
973
+ glPixelStorei(Gl::GL_PACK_ROW_LENGTH, 0)
974
+ glPixelStorei(Gl::GL_PACK_SKIP_ROWS, 0)
975
+ glPixelStorei(Gl::GL_PACK_SKIP_PIXELS, 0)
976
+
977
+ glReadPixels(0, 0, self.width, self.height, Gl::GL_ALPHA, Gl::GL_UNSIGNED_BYTE)
978
+ end
979
+
980
+ end
981
+ end