aims_project_windows 0.3.1
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.
- data/README +5 -0
- data/bin/AimsCalc +367 -0
- data/bin/AimsProject +88 -0
- data/bin/AimsProjectManager +39 -0
- data/lib/aims_project/aims_project_exception.rb +42 -0
- data/lib/aims_project/aims_project_geometry.rb +52 -0
- data/lib/aims_project/app_controller.rb +245 -0
- data/lib/aims_project/atom.rb +95 -0
- data/lib/aims_project/calculation.rb +406 -0
- data/lib/aims_project/calculation_tree.rb +65 -0
- data/lib/aims_project/calculation_window.rb +141 -0
- data/lib/aims_project/crystal_viewer.rb +994 -0
- data/lib/aims_project/crystal_viewer_options.rb +103 -0
- data/lib/aims_project/geometry_console.rb +155 -0
- data/lib/aims_project/geometry_editor.rb +83 -0
- data/lib/aims_project/geometry_file.rb +183 -0
- data/lib/aims_project/geometry_window.rb +160 -0
- data/lib/aims_project/green_arrow.jpg +0 -0
- data/lib/aims_project/inspector.rb +183 -0
- data/lib/aims_project/material.rb +30 -0
- data/lib/aims_project/octree.rb +5 -0
- data/lib/aims_project/pan.gif +0 -0
- data/lib/aims_project/project.rb +102 -0
- data/lib/aims_project/project_tree.rb +62 -0
- data/lib/aims_project/rotate.gif +0 -0
- data/lib/aims_project/thread_callback_event.rb +19 -0
- data/lib/aims_project/zoom.gif +0 -0
- data/lib/aims_project.rb +158 -0
- data/skeleton/Capfile +37 -0
- data/skeleton/config/aims.sh +58 -0
- data/skeleton/config/tasks.rb +145 -0
- data/skeleton/config/user_variables.rb +37 -0
- data/skeleton/control/example.erb +41 -0
- data/skeleton/geometry/example +1 -0
- metadata +137 -0
@@ -0,0 +1,994 @@
|
|
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
|
+
draw_scene
|
218
|
+
|
219
|
+
end
|
220
|
+
|
221
|
+
def unit_cell_changed
|
222
|
+
init_clip_planes
|
223
|
+
draw_scene
|
224
|
+
end
|
225
|
+
|
226
|
+
def init_clip_planes
|
227
|
+
|
228
|
+
Thread.new(self) { |evtHandler|
|
229
|
+
@unit_cell_corrected = @unit_cell.correct
|
230
|
+
evt = ThreadCallbackEvent.new
|
231
|
+
evtHandler.add_pending_event(evt)
|
232
|
+
}
|
233
|
+
|
234
|
+
return unless (@unit_cell and @unit_cell.is_valid?)
|
235
|
+
|
236
|
+
# each bounding box is a 2 element array [max, min]
|
237
|
+
bounding_box = @unit_cell.bounding_box(false)
|
238
|
+
xmax = bounding_box[0].x
|
239
|
+
xmin = bounding_box[1].x
|
240
|
+
ymax = bounding_box[0].y
|
241
|
+
ymin = bounding_box[1].y
|
242
|
+
zmax = bounding_box[0].z
|
243
|
+
zmin = bounding_box[1].z
|
244
|
+
|
245
|
+
@xmax_plane = Plane.new( 1, 0, 0, xmax, 0, 0)
|
246
|
+
@xmin_plane = Plane.new(-1, 0, 0, xmin, 0, 0)
|
247
|
+
@ymax_plane = Plane.new( 0, 1, 0, 0, ymax, 0)
|
248
|
+
@ymin_plane = Plane.new( 0,-1, 0, 0, ymin, 0)
|
249
|
+
@zmax_plane = Plane.new( 0, 0, 1, 0, 0, zmax)
|
250
|
+
@zmin_plane = Plane.new( 0, 0,-1, 0, 0, zmin)
|
251
|
+
|
252
|
+
# Add clip-planes to each unit cell
|
253
|
+
@unit_cell.clear_planes
|
254
|
+
@unit_cell.add_plane(@xmax_plane, false)
|
255
|
+
@unit_cell.add_plane(@xmin_plane, false)
|
256
|
+
@unit_cell.add_plane(@ymax_plane, false)
|
257
|
+
@unit_cell.add_plane(@ymin_plane, false)
|
258
|
+
@unit_cell.add_plane(@zmax_plane, false)
|
259
|
+
@unit_cell.add_plane(@zmin_plane, false)
|
260
|
+
@unit_cell.recache_visible_atoms
|
261
|
+
@unit_cell.make_bonds(@options.bond_length)
|
262
|
+
|
263
|
+
end
|
264
|
+
|
265
|
+
def dump_properties
|
266
|
+
self.instance_variables.each{|v|
|
267
|
+
puts "#{v} = #{self.instance_variable_get(v)}"
|
268
|
+
}
|
269
|
+
end
|
270
|
+
|
271
|
+
# The currently displayed unit cell
|
272
|
+
def current_unit_cell
|
273
|
+
self.unit_cell
|
274
|
+
end
|
275
|
+
|
276
|
+
def dump_geometry
|
277
|
+
atoms = self.unit_cell
|
278
|
+
if atoms
|
279
|
+
puts atoms.format_geometry_in
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
def bond_length=(l)
|
284
|
+
@options.bond_length = l
|
285
|
+
# FIXME The bonds should be made in the controller
|
286
|
+
# self.unit_cell.each{|uc| uc.make_bonds(l)} if self.unit_cell
|
287
|
+
end
|
288
|
+
|
289
|
+
def mouse_down(x,y)
|
290
|
+
self.x_down = x
|
291
|
+
self.y_down = y
|
292
|
+
self.x_last = x
|
293
|
+
self.y_last = y
|
294
|
+
self.loRes
|
295
|
+
end
|
296
|
+
|
297
|
+
def mouse_up(x,y)
|
298
|
+
return unless self.unit_cell
|
299
|
+
@picked = pick_object(x,y)
|
300
|
+
self.atom = self.unit_cell.atoms.find{|a| a.id == @picked[:atoms].last}
|
301
|
+
if self.atom
|
302
|
+
@controller.select_atom(self.atom)
|
303
|
+
# puts self.atom.format_geometry_in
|
304
|
+
end
|
305
|
+
|
306
|
+
unless @picked[:planes].empty?
|
307
|
+
clip_plane_id = @picked[:planes].first
|
308
|
+
@active_clip_plane = case clip_plane_id
|
309
|
+
when 1
|
310
|
+
@zmax_plane
|
311
|
+
when 2
|
312
|
+
@zmin_plane
|
313
|
+
when 3
|
314
|
+
@xmax_plane
|
315
|
+
when 4
|
316
|
+
@xmin_plane
|
317
|
+
when 5
|
318
|
+
@ymax_plane
|
319
|
+
when 6
|
320
|
+
@ymin_plane
|
321
|
+
else
|
322
|
+
nil
|
323
|
+
end
|
324
|
+
if @active_clip_plane
|
325
|
+
set_mouse_motion_function(:move_clip_plane)
|
326
|
+
end
|
327
|
+
end
|
328
|
+
self.hiRes
|
329
|
+
|
330
|
+
# Harmless correction for bug in WxRuby that doesn't register all mouse down events
|
331
|
+
self.x_last = nil
|
332
|
+
self.y_last = nil
|
333
|
+
|
334
|
+
glutPostRedisplay if @using_glut
|
335
|
+
end
|
336
|
+
|
337
|
+
def mouse_dragged(x, y)
|
338
|
+
|
339
|
+
# Harmless correction for bug in WxRuby that doesn't register all mouse down events
|
340
|
+
self.x_last = x if self.x_last.nil?
|
341
|
+
self.y_last = y if self.y_last.nil?
|
342
|
+
self.z_last = z if self.z_last.nil?
|
343
|
+
|
344
|
+
case self.mouse_motion_func
|
345
|
+
when :rotate
|
346
|
+
rotate(x,y)
|
347
|
+
when :zoom
|
348
|
+
zoom(x,y)
|
349
|
+
when :pan
|
350
|
+
pan(x,y)
|
351
|
+
when :move_clip_plane
|
352
|
+
move_clip_plane(x,y)
|
353
|
+
else
|
354
|
+
rotate(x,y)
|
355
|
+
end
|
356
|
+
glutPostRedisplay if @using_glut
|
357
|
+
end
|
358
|
+
|
359
|
+
def delete_atom
|
360
|
+
if self.atom
|
361
|
+
# Remove
|
362
|
+
self.unit_cell.remove_atom(self.atom)
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
# Unproject the mouse coordinates to model space
|
367
|
+
# Handles the flipping of the y-coordinate
|
368
|
+
def unproject(x,y,z)
|
369
|
+
model = glGetDoublev(GL_MODELVIEW_MATRIX)
|
370
|
+
proj = glGetDoublev(GL_PROJECTION_MATRIX)
|
371
|
+
viewport = glGetIntegerv(GL_VIEWPORT);
|
372
|
+
|
373
|
+
gluUnProject(x, viewport[3]-y, z, model, proj, viewport)
|
374
|
+
end
|
375
|
+
|
376
|
+
# Cast a ray from the near clip-plane to the far clip-plane through the point x,y
|
377
|
+
# return a two element array
|
378
|
+
# element 0 is the model space coordinates of (x,y) on the near clip plane
|
379
|
+
# element 1 is the direction of the ray
|
380
|
+
def cast_ray_to(x,y)
|
381
|
+
x1,y1,z1 = unproject(x,y,0)
|
382
|
+
x2,y2,z2 = unproject(x,y,1)
|
383
|
+
[ Vector[x1, y1, z1], Vector[x2-x1, y2-y1, z2-z1] ]
|
384
|
+
end
|
385
|
+
|
386
|
+
def move_clip_plane(x,y)
|
387
|
+
|
388
|
+
x_obj_last, y_obj_last, z_obj_last = unproject(x_last, y_last, 0)
|
389
|
+
x_obj, y_obj, z_obj = unproject(x,y,0)
|
390
|
+
|
391
|
+
w = [x_obj - x_obj_last, y_obj - y_obj_last, z_obj - z_obj_last]
|
392
|
+
n = @active_clip_plane.unit_normal
|
393
|
+
d = w[0]*n[0] + w[1]*n[1] + w[2]*n[2]
|
394
|
+
|
395
|
+
# scale = 0.1
|
396
|
+
# dx = (x - self.x_last)
|
397
|
+
# dy = (y - self.y_last)
|
398
|
+
# dr = sqrt(dx*dx + dy*dy)*(0 > dy ? -1 : 1)*scale
|
399
|
+
self.x_last = x
|
400
|
+
self.y_last = y
|
401
|
+
|
402
|
+
@active_clip_plane.displace_along_normal(d)
|
403
|
+
self.atoms_changed = true
|
404
|
+
end
|
405
|
+
|
406
|
+
def rotate(x,y)
|
407
|
+
self.az += 5*(x - self.x_last)
|
408
|
+
self.alt += 5*(y - self.y_last)
|
409
|
+
self.x_last = x
|
410
|
+
self.y_last = y
|
411
|
+
end
|
412
|
+
|
413
|
+
def zoom(x,y)
|
414
|
+
if self.orthographic
|
415
|
+
self.ortho_side -= (y - self.y_last)*0.1
|
416
|
+
else
|
417
|
+
self.offz -= (y - self.y_last)*0.1
|
418
|
+
end
|
419
|
+
self.x_last = x
|
420
|
+
self.y_last = y
|
421
|
+
end
|
422
|
+
|
423
|
+
def pan(x,y)
|
424
|
+
|
425
|
+
z = 0.0 # This value is normalized with respect to the near and far clip planes
|
426
|
+
|
427
|
+
obj_x, obj_y, obj_z = unproject(x, y, z)
|
428
|
+
obj_x_last, obj_y_last, obj_z_last = unproject(x_last, y_last, z_last)
|
429
|
+
@center.x += obj_x_last - obj_x
|
430
|
+
@center.y += obj_y_last - obj_y
|
431
|
+
@center.z += obj_z_last - obj_z
|
432
|
+
|
433
|
+
self.x_last = x
|
434
|
+
self.y_last = y
|
435
|
+
self.z_last = z
|
436
|
+
end
|
437
|
+
|
438
|
+
def loRes
|
439
|
+
self.slices = 5
|
440
|
+
self.stacks = 5
|
441
|
+
end
|
442
|
+
|
443
|
+
def hiRes
|
444
|
+
self.slices = 20
|
445
|
+
self.stacks = 20
|
446
|
+
end
|
447
|
+
|
448
|
+
def set_view(offset_x, offset_y, offset_z, alt, az)
|
449
|
+
self.offx = offset_x
|
450
|
+
self.offy = offset_y
|
451
|
+
self.offz = offset_z
|
452
|
+
self.alt = alt
|
453
|
+
self.az = az
|
454
|
+
end
|
455
|
+
|
456
|
+
# Executes the named method when the mouse is dragged
|
457
|
+
def set_mouse_motion_function(method_name)
|
458
|
+
self.mouse_motion_func = method_name.to_sym
|
459
|
+
end
|
460
|
+
|
461
|
+
def draw_scene
|
462
|
+
|
463
|
+
@glPanel.set_current
|
464
|
+
sz = @glPanel.size
|
465
|
+
viewport_setup(sz.width, sz.height)
|
466
|
+
|
467
|
+
|
468
|
+
begin
|
469
|
+
draw_init
|
470
|
+
apply_projection
|
471
|
+
position_camera
|
472
|
+
add_lights if @options.show_lighting
|
473
|
+
outline_supercell if @options.show_supercell
|
474
|
+
|
475
|
+
if self.unit_cell
|
476
|
+
atoms = self.unit_cell
|
477
|
+
@options.x_repeat.times do |i|
|
478
|
+
@options.y_repeat.times do |j|
|
479
|
+
@options.z_repeat.times do |k|
|
480
|
+
|
481
|
+
origin = if self.unit_cell.lattice_vectors
|
482
|
+
atoms.lattice_vectors[0]*i + atoms.lattice_vectors[1]*j + atoms.lattice_vectors[2]*k
|
483
|
+
else
|
484
|
+
Vector[0, 0, 0]
|
485
|
+
end
|
486
|
+
|
487
|
+
draw_bonds(origin) if @options.show_bonds
|
488
|
+
draw_lattice(origin)
|
489
|
+
end
|
490
|
+
end
|
491
|
+
end
|
492
|
+
end
|
493
|
+
draw_clip_planes
|
494
|
+
rescue GeometryEvaluationException => e
|
495
|
+
rescue AimsProjectException => e
|
496
|
+
puts e.message
|
497
|
+
puts e.backtrace.join("\n")
|
498
|
+
end
|
499
|
+
|
500
|
+
@glPanel.swap_buffers
|
501
|
+
end
|
502
|
+
|
503
|
+
def viewport_setup(width, height)
|
504
|
+
self.width = width
|
505
|
+
self.height = height
|
506
|
+
glViewport(0, 0, self.width, self.height)
|
507
|
+
end
|
508
|
+
|
509
|
+
def apply_projection
|
510
|
+
glMatrixMode(GL_PROJECTION)
|
511
|
+
glLoadIdentity()
|
512
|
+
if self.orthographic
|
513
|
+
glOrtho(-self.ortho_side, self.ortho_side, -self.ortho_side,self.ortho_side, self.ortho_zmin, self.ortho_zmax)
|
514
|
+
else
|
515
|
+
gluPerspective(60, self.width/self.height, 1, 120)
|
516
|
+
end
|
517
|
+
glMatrixMode(GL_MODELVIEW)
|
518
|
+
end
|
519
|
+
|
520
|
+
def draw_init
|
521
|
+
glClearColor(background.r,background.g,background.b,@options.solid_bg)
|
522
|
+
glEnable(GL_DEPTH_TEST)
|
523
|
+
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
|
524
|
+
|
525
|
+
|
526
|
+
# Antialiasing
|
527
|
+
glEnable(GL_LINE_SMOOTH)
|
528
|
+
glEnable(GL_BLEND)
|
529
|
+
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
|
530
|
+
glHint(GL_LINE_SMOOTH_HINT, GL_NICEST)
|
531
|
+
glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST)
|
532
|
+
|
533
|
+
if @options.show_lighting
|
534
|
+
glEnable(GL_LIGHTING)
|
535
|
+
glEnable(GL_LIGHT0)
|
536
|
+
else
|
537
|
+
glDisable(GL_LIGHTING)
|
538
|
+
end
|
539
|
+
|
540
|
+
# Point size
|
541
|
+
glPointSize(5)
|
542
|
+
glLineWidth(2)
|
543
|
+
end
|
544
|
+
|
545
|
+
def pick_object(x,y)
|
546
|
+
|
547
|
+
buf = glSelectBuffer(512)
|
548
|
+
glRenderMode(GL_SELECT)
|
549
|
+
model = glGetDoublev(GL_MODELVIEW_MATRIX)
|
550
|
+
proj = glGetDoublev(GL_PROJECTION_MATRIX)
|
551
|
+
viewport = glGetIntegerv(GL_VIEWPORT);
|
552
|
+
|
553
|
+
self.picking = true
|
554
|
+
glMatrixMode(GL_PROJECTION)
|
555
|
+
glLoadIdentity()
|
556
|
+
gluPickMatrix(x,viewport[3]-y,5,5,viewport)
|
557
|
+
if self.orthographic
|
558
|
+
glOrtho(-self.ortho_side, self.ortho_side, -self.ortho_side,self.ortho_side, self.ortho_zmin, self.ortho_zmax)
|
559
|
+
else
|
560
|
+
gluPerspective(60, self.width/self.height, 1, 60)
|
561
|
+
end
|
562
|
+
|
563
|
+
z = 0.0 # This value is normalized with respect to the near and far clip planes
|
564
|
+
obj_x, obj_y, obj_z = gluUnProject(x, viewport[3]-y, z, model, proj, viewport)
|
565
|
+
|
566
|
+
glMatrixMode(GL_MODELVIEW)
|
567
|
+
|
568
|
+
glInitNames
|
569
|
+
if self.unit_cell
|
570
|
+
atoms = self.unit_cell
|
571
|
+
@options.x_repeat.times do |i|
|
572
|
+
@options.y_repeat.times do |j|
|
573
|
+
@options.z_repeat.times do |k|
|
574
|
+
|
575
|
+
# Hack to display non-periodic systems
|
576
|
+
origin = if self.unit_cell.lattice_vectors
|
577
|
+
atoms.lattice_vectors[0]*i + atoms.lattice_vectors[1]*j + atoms.lattice_vectors[2]*k
|
578
|
+
else
|
579
|
+
Vector[0,0,0]
|
580
|
+
end
|
581
|
+
|
582
|
+
self.draw_lattice(origin)
|
583
|
+
end
|
584
|
+
end
|
585
|
+
end
|
586
|
+
end
|
587
|
+
self.draw_clip_planes
|
588
|
+
|
589
|
+
self.picking = false
|
590
|
+
|
591
|
+
glMatrixMode(GL_MODELVIEW);
|
592
|
+
glFlush();
|
593
|
+
|
594
|
+
count = glRenderMode(GL_RENDER)
|
595
|
+
data = buf.unpack("L!*")
|
596
|
+
names = []
|
597
|
+
count.times do
|
598
|
+
num_names = data.shift
|
599
|
+
min_depth = data.shift
|
600
|
+
max_depth = data.shift
|
601
|
+
num_names.times do
|
602
|
+
names << data.shift
|
603
|
+
end
|
604
|
+
end
|
605
|
+
|
606
|
+
picked_objects = {:atoms => [], :planes => [], :bonds => []}
|
607
|
+
names.each{|n|
|
608
|
+
if (n & PICK_ID_ATOM) == PICK_ID_ATOM
|
609
|
+
picked_objects[:atoms] << (n ^ PICK_ID_ATOM)
|
610
|
+
end
|
611
|
+
if (n & PICK_ID_PLANE) == PICK_ID_PLANE
|
612
|
+
picked_objects[:planes] << (n ^ PICK_ID_PLANE)
|
613
|
+
end
|
614
|
+
}
|
615
|
+
|
616
|
+
picked_objects
|
617
|
+
end
|
618
|
+
|
619
|
+
def position_camera
|
620
|
+
return unless self.unit_cell
|
621
|
+
atoms = self.unit_cell
|
622
|
+
|
623
|
+
# Find the center of all atoms, not just visible ones.
|
624
|
+
@center = atoms.center unless @center
|
625
|
+
|
626
|
+
# Move camera out along z-axis
|
627
|
+
glMatrixMode(GL_MODELVIEW)
|
628
|
+
glLoadIdentity()
|
629
|
+
|
630
|
+
glTranslatef(self.offx,self.offy,self.offz)
|
631
|
+
glRotatef(self.alt, 1, 0, 0)
|
632
|
+
glRotatef(self.az, 0, 0, 1)
|
633
|
+
glTranslatef(-@center.x, -@center.y, -@center.z)
|
634
|
+
end
|
635
|
+
|
636
|
+
def add_lights
|
637
|
+
light0_position = [1,1,1,0]
|
638
|
+
light1_position = [-1,-1,1,1]
|
639
|
+
glLightModel(GL_LIGHT_MODEL_AMBIENT, [0.6, 0.6, 0.6 ,1.0])
|
640
|
+
# glLightModel(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_TRUE)
|
641
|
+
glLightfv(GL_LIGHT0, GL_POSITION, light0_position)
|
642
|
+
glLightfv(GL_LIGHT1, GL_POSITION, light1_position)
|
643
|
+
end
|
644
|
+
|
645
|
+
def outline_supercell
|
646
|
+
return unless self.unit_cell
|
647
|
+
uc = self.unit_cell
|
648
|
+
|
649
|
+
vecs = uc.lattice_vectors
|
650
|
+
return unless vecs
|
651
|
+
|
652
|
+
origin = [0, 0, 0]
|
653
|
+
v1 = vecs[0]
|
654
|
+
v2 = vecs[1]
|
655
|
+
v3 = vecs[2]
|
656
|
+
|
657
|
+
# Corner #1
|
658
|
+
c1 = v1 + v3
|
659
|
+
|
660
|
+
# Corner #2
|
661
|
+
c2 = v2 + v3
|
662
|
+
|
663
|
+
# Corner #3
|
664
|
+
c3 = v1 + v2
|
665
|
+
|
666
|
+
# Corner #4
|
667
|
+
c4 = v1 + v2 + v3
|
668
|
+
|
669
|
+
Material.black.apply
|
670
|
+
glLineWidth(1.0)
|
671
|
+
|
672
|
+
glBegin(GL_LINES)
|
673
|
+
|
674
|
+
glVertex3f(origin[0], origin[1], origin[2])
|
675
|
+
glVertex3f(v1[0], v1[1], v1[2])
|
676
|
+
|
677
|
+
glVertex3f(origin[0], origin[1], origin[2])
|
678
|
+
glVertex3f(v2[0], v2[1], v2[2])
|
679
|
+
|
680
|
+
glVertex3f(origin[0], origin[1], origin[2])
|
681
|
+
glVertex3f(v3[0], v3[1], v3[2])
|
682
|
+
|
683
|
+
glVertex3f(v1[0], v1[1], v1[2])
|
684
|
+
glVertex3f(c3[0], c3[1], c3[2])
|
685
|
+
|
686
|
+
glVertex3f(v2[0], v2[1], v2[2])
|
687
|
+
glVertex3f(c3[0], c3[1], c3[2])
|
688
|
+
|
689
|
+
glVertex3f(c3[0], c3[1], c3[2])
|
690
|
+
glVertex3f(c4[0], c4[1], c4[2])
|
691
|
+
|
692
|
+
glVertex3f(v1[0], v1[1], v1[2])
|
693
|
+
glVertex3f(c1[0], c1[1], c1[2])
|
694
|
+
|
695
|
+
glVertex3f(v2[0], v2[1], v2[2])
|
696
|
+
glVertex3f(c2[0], c2[1], c2[2])
|
697
|
+
|
698
|
+
glVertex3f(c1[0], c1[1], c1[2])
|
699
|
+
glVertex3f(c4[0], c4[1], c4[2])
|
700
|
+
|
701
|
+
glVertex3f(c2[0], c2[1], c2[2])
|
702
|
+
glVertex3f(c4[0], c4[1], c4[2])
|
703
|
+
|
704
|
+
glVertex3f(v3[0], v3[1], v3[2])
|
705
|
+
glVertex3f(c1[0], c1[1], c1[2])
|
706
|
+
|
707
|
+
glVertex3f(v3[0], v3[1], v3[2])
|
708
|
+
glVertex3f(c2[0], c2[1], c2[2])
|
709
|
+
|
710
|
+
glEnd()
|
711
|
+
end
|
712
|
+
|
713
|
+
def draw_bonds(origin = [0,0,0])
|
714
|
+
return unless self.unit_cell
|
715
|
+
atoms = if @options.correct
|
716
|
+
@unit_cell_corrected
|
717
|
+
else
|
718
|
+
@unit_cell
|
719
|
+
end
|
720
|
+
return unless atoms.bonds
|
721
|
+
|
722
|
+
Material.black.apply
|
723
|
+
glLineWidth(1.0)
|
724
|
+
glBegin(GL_LINES)
|
725
|
+
atoms.bonds.each{|b|
|
726
|
+
glVertex3f(origin[0] + b[0].x, origin[1] + b[0].y, origin[2] + b[0].z)
|
727
|
+
glVertex3f(origin[0] + b[1].x, origin[1] + b[1].y, origin[2] + b[1].z)
|
728
|
+
}
|
729
|
+
glEnd()
|
730
|
+
end
|
731
|
+
|
732
|
+
#
|
733
|
+
# Draw an atom
|
734
|
+
# @param x The x-coordinate
|
735
|
+
# @param y The y-coordinate
|
736
|
+
# @param z The z-coordinate
|
737
|
+
# @param r The radius
|
738
|
+
# @param name The name of the sphere (for picking)
|
739
|
+
# @return a sphere_quadric for reuse if desired. Make sure to delete it when done
|
740
|
+
def draw_sphere(x,y,z,r, name, sphere_quadric=nil)
|
741
|
+
|
742
|
+
unless sphere_quadric
|
743
|
+
sphere_quadric = gluNewQuadric()
|
744
|
+
gluQuadricDrawStyle(sphere_quadric, GLU_FILL)
|
745
|
+
gluQuadricNormals(sphere_quadric, GLU_SMOOTH)
|
746
|
+
gluQuadricOrientation(sphere_quadric, GLU_OUTSIDE)
|
747
|
+
end
|
748
|
+
|
749
|
+
# Load a new matrix onto the stack
|
750
|
+
glPushMatrix()
|
751
|
+
glPushName(name | PICK_ID_ATOM) if self.picking
|
752
|
+
glTranslatef(x, y, z)
|
753
|
+
gluSphere(sphere_quadric, r, slices, stacks)
|
754
|
+
glPopName() if picking
|
755
|
+
glPopMatrix()
|
756
|
+
|
757
|
+
sphere_quadric
|
758
|
+
end
|
759
|
+
|
760
|
+
def draw_lattice(origin = [0,0,0])
|
761
|
+
|
762
|
+
return unless self.unit_cell
|
763
|
+
|
764
|
+
atoms = if @options.correct
|
765
|
+
@unit_cell_corrected
|
766
|
+
else
|
767
|
+
@unit_cell
|
768
|
+
end
|
769
|
+
|
770
|
+
return unless atoms
|
771
|
+
|
772
|
+
# Create sphere object
|
773
|
+
rmin = 0.2
|
774
|
+
rmax = 0.5
|
775
|
+
|
776
|
+
if self.atoms_changed
|
777
|
+
atoms.recache_visible_atoms
|
778
|
+
self.atoms_changed = false
|
779
|
+
end
|
780
|
+
|
781
|
+
# Calculate radius scaling factor vs. depth
|
782
|
+
rrange = rmax - rmin
|
783
|
+
# bb = atoms.bounding_box
|
784
|
+
zmin = atoms.min{|a,b| a.z <=> b.z}.z
|
785
|
+
zmax = atoms.max{|a,b| a.z <=> b.z}.z
|
786
|
+
zrange = zmax - zmin
|
787
|
+
if zrange == 0
|
788
|
+
rscale = 0
|
789
|
+
else
|
790
|
+
rscale = rrange/zrange
|
791
|
+
end
|
792
|
+
|
793
|
+
|
794
|
+
|
795
|
+
sphere_quadric = nil
|
796
|
+
for a in atoms
|
797
|
+
a.material.apply(@options.show_lighting)
|
798
|
+
case self.render_mode
|
799
|
+
when :ball_stick
|
800
|
+
r = rmin+(a.z - zmin)*rscale
|
801
|
+
else
|
802
|
+
r = 2.0
|
803
|
+
end
|
804
|
+
|
805
|
+
sphere_quadric = draw_sphere(origin[0] + a.x, origin[1] + a.y, origin[2] + a.z, r, a.id, sphere_quadric)
|
806
|
+
|
807
|
+
end
|
808
|
+
gluDeleteQuadric(sphere_quadric) if sphere_quadric
|
809
|
+
|
810
|
+
end
|
811
|
+
|
812
|
+
# a test method to see of an object of a particular type was picked.
|
813
|
+
# valid types are :atom, :plane
|
814
|
+
def is_picked?(type, id)
|
815
|
+
if @picked
|
816
|
+
case type
|
817
|
+
when :atom
|
818
|
+
@picked[:atoms].member? id
|
819
|
+
when :plane
|
820
|
+
@picked[:planes].member? id
|
821
|
+
when :bond
|
822
|
+
@picked[:bonds].member? id
|
823
|
+
else
|
824
|
+
false
|
825
|
+
end
|
826
|
+
else
|
827
|
+
false
|
828
|
+
end
|
829
|
+
end
|
830
|
+
|
831
|
+
#
|
832
|
+
# Draw a plane
|
833
|
+
# use the vertices defined in lineLoop as the boundary of the plane
|
834
|
+
def draw_plane(plane, lineLoop, pickid)
|
835
|
+
|
836
|
+
glPushName(pickid | PICK_ID_PLANE) if self.picking
|
837
|
+
|
838
|
+
# if is_picked?(:plane, pickid)
|
839
|
+
# Material.black.apply
|
840
|
+
# glLineWidth(3.0)
|
841
|
+
# glEdgeFlag(GL_TRUE)
|
842
|
+
# end
|
843
|
+
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
|
844
|
+
|
845
|
+
Material.new(0.9, 0.9, 0.0, 0.5).apply
|
846
|
+
glBegin(GL_TRIANGLE_FAN)
|
847
|
+
glNormal3f(plane.a, plane.b, plane.c)
|
848
|
+
lineLoop.each{|p|
|
849
|
+
glVertex3f(p[0], p[1], p[2])
|
850
|
+
}
|
851
|
+
glEnd()
|
852
|
+
|
853
|
+
glEdgeFlag(GL_FALSE)
|
854
|
+
glPopName() if picking
|
855
|
+
|
856
|
+
if (is_picked?(:plane, pickid))
|
857
|
+
Material.black.apply
|
858
|
+
glLineWidth(3.0)
|
859
|
+
glBegin(GL_LINE_LOOP)
|
860
|
+
lineLoop.each{|p|
|
861
|
+
glVertex3f(p[0], p[1], p[2])
|
862
|
+
}
|
863
|
+
glEnd()
|
864
|
+
glLineWidth(1.0)
|
865
|
+
end
|
866
|
+
|
867
|
+
end
|
868
|
+
|
869
|
+
def draw_clip_planes
|
870
|
+
return unless self.unit_cell
|
871
|
+
atoms = self.unit_cell
|
872
|
+
|
873
|
+
bb = atoms.bounding_box
|
874
|
+
bbx1 = bb[0].x
|
875
|
+
bbx2 = bb[1].x
|
876
|
+
bby1 = bb[0].y
|
877
|
+
bby2 = bb[1].y
|
878
|
+
bbz1 = bb[0].z
|
879
|
+
bbz2 = bb[1].z
|
880
|
+
|
881
|
+
# draw z_planes
|
882
|
+
# The are bounded by the min and max points in the x-y plane
|
883
|
+
|
884
|
+
if @options.show_zclip and @zmax_plane and @zmin_plane
|
885
|
+
z = -1*@zmax_plane.distance_to_point(0,0,0)
|
886
|
+
draw_plane(@zmax_plane, [[bbx1, bby1, z],
|
887
|
+
[bbx2, bby1, z],
|
888
|
+
[bbx2, bby2, z],
|
889
|
+
[bbx1, bby2, z]],1)
|
890
|
+
|
891
|
+
z = @zmin_plane.distance_to_point(0,0,0)
|
892
|
+
draw_plane(@zmin_plane, [[bbx1, bby1, z],
|
893
|
+
[bbx2, bby1, z],
|
894
|
+
[bbx2, bby2, z],
|
895
|
+
[bbx1, bby2, z]],2)
|
896
|
+
end
|
897
|
+
|
898
|
+
if @options.show_xclip and @xmax_plane and @xmin_plane
|
899
|
+
x = -1*@xmax_plane.distance_to_point(0,0,0)
|
900
|
+
draw_plane(@xmax_plane, [[x, bby1, bbz1],
|
901
|
+
[x, bby1, bbz2],
|
902
|
+
[x, bby2, bbz2],
|
903
|
+
[x, bby2, bbz1]],3)
|
904
|
+
|
905
|
+
x = @xmin_plane.distance_to_point(0,0,0)
|
906
|
+
draw_plane(@xmin_plane, [[x, bby1, bbz1],
|
907
|
+
[x, bby1, bbz2],
|
908
|
+
[x, bby2, bbz2],
|
909
|
+
[x, bby2, bbz1]],4)
|
910
|
+
end
|
911
|
+
|
912
|
+
if @options.show_yclip and @ymax_plane and @ymin_plane
|
913
|
+
y = -1*@ymax_plane.distance_to_point(0,0,0)
|
914
|
+
draw_plane(@ymax_plane, [[bbx1, y, bbz1],
|
915
|
+
[bbx1, y, bbz2],
|
916
|
+
[bbx2, y, bbz2],
|
917
|
+
[bbx2, y, bbz1]],5)
|
918
|
+
|
919
|
+
y = @ymin_plane.distance_to_point(0,0,0)
|
920
|
+
draw_plane(@ymin_plane, [[bbx1, y, bbz1],
|
921
|
+
[bbx1, y, bbz2],
|
922
|
+
[bbx2, y, bbz2],
|
923
|
+
[bbx2, y, bbz1]],6)
|
924
|
+
end
|
925
|
+
|
926
|
+
end
|
927
|
+
|
928
|
+
def rgba_image_data
|
929
|
+
|
930
|
+
# 4 bytes/pixel
|
931
|
+
bytewidth = self.width*4
|
932
|
+
bytewidth = (bytewidth.to_i + 3) & ~3 # Align to 4 bytes
|
933
|
+
bytes = bytewidth * self.height
|
934
|
+
|
935
|
+
# Finish any pending Commands
|
936
|
+
@glPanel.set_current
|
937
|
+
glFinish
|
938
|
+
|
939
|
+
# Setup pixel store
|
940
|
+
glPixelStorei(Gl::GL_PACK_ALIGNMENT, 4) # Force 4-byte alignment
|
941
|
+
glPixelStorei(Gl::GL_PACK_ROW_LENGTH, 0)
|
942
|
+
glPixelStorei(Gl::GL_PACK_SKIP_ROWS, 0)
|
943
|
+
glPixelStorei(Gl::GL_PACK_SKIP_PIXELS, 0)
|
944
|
+
|
945
|
+
glReadPixels(0, 0, self.width, self.height, Gl::GL_RGBA, Gl::GL_UNSIGNED_BYTE)
|
946
|
+
|
947
|
+
end
|
948
|
+
|
949
|
+
def image
|
950
|
+
image = Image.new(self.width, self.height)
|
951
|
+
image.set_rgb_data(self.rgb_image_data)
|
952
|
+
image.set_alpha_data(self.alpha_image_data)
|
953
|
+
image
|
954
|
+
end
|
955
|
+
|
956
|
+
def rgb_image_data
|
957
|
+
|
958
|
+
# 3 bytes/pixel
|
959
|
+
bytewidth = self.width*3
|
960
|
+
bytes = bytewidth * self.height
|
961
|
+
|
962
|
+
# Finish any pending Commands
|
963
|
+
@glPanel.set_current
|
964
|
+
glFinish
|
965
|
+
|
966
|
+
# Setup pixel store
|
967
|
+
glPixelStorei(Gl::GL_PACK_ALIGNMENT, 1) # Force byte alignment
|
968
|
+
glPixelStorei(Gl::GL_PACK_ROW_LENGTH, 0)
|
969
|
+
glPixelStorei(Gl::GL_PACK_SKIP_ROWS, 0)
|
970
|
+
glPixelStorei(Gl::GL_PACK_SKIP_PIXELS, 0)
|
971
|
+
|
972
|
+
glReadPixels(0, 0, self.width, self.height, Gl::GL_RGB, Gl::GL_UNSIGNED_BYTE)
|
973
|
+
|
974
|
+
end
|
975
|
+
|
976
|
+
def alpha_image_data
|
977
|
+
# 1 byte/pixel
|
978
|
+
bytewidth = self.width;
|
979
|
+
|
980
|
+
bytes = bytewidth*height;
|
981
|
+
@glPanel.set_current
|
982
|
+
glFinish
|
983
|
+
|
984
|
+
# Setup pixel store
|
985
|
+
glPixelStorei(Gl::GL_PACK_ALIGNMENT, 1) # Force byte alignment
|
986
|
+
glPixelStorei(Gl::GL_PACK_ROW_LENGTH, 0)
|
987
|
+
glPixelStorei(Gl::GL_PACK_SKIP_ROWS, 0)
|
988
|
+
glPixelStorei(Gl::GL_PACK_SKIP_PIXELS, 0)
|
989
|
+
|
990
|
+
glReadPixels(0, 0, self.width, self.height, Gl::GL_ALPHA, Gl::GL_UNSIGNED_BYTE)
|
991
|
+
end
|
992
|
+
|
993
|
+
end
|
994
|
+
end
|