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,406 @@
|
|
1
|
+
|
2
|
+
require 'fileutils'
|
3
|
+
require 'erb'
|
4
|
+
require 'date'
|
5
|
+
|
6
|
+
#
|
7
|
+
# A calculation is a combination of a geometry, a control file, and an output
|
8
|
+
# Each calculation runs in its own directory
|
9
|
+
# A special file named .calc_status in the calculation directory reveals the status of the calculation
|
10
|
+
# The format of this file is still under consideration.
|
11
|
+
# possible options are:
|
12
|
+
# • A single word indicating the status
|
13
|
+
# • A single word on the first line and unstructured text following (for comments, history, errors)
|
14
|
+
# • YAML
|
15
|
+
#
|
16
|
+
# The calculation progresses through the following state machine
|
17
|
+
# STAGED:
|
18
|
+
# This is the initial stage, before the calculation has been
|
19
|
+
# moved to the computation server and queued for execution.
|
20
|
+
# Valid inputs are CANCEL, ENQUEUE
|
21
|
+
# QUEUED:
|
22
|
+
# Calculation is uploaded to computation server and queued for execution.
|
23
|
+
# Valid inputs are CANCEL, PROGRESS
|
24
|
+
# RUNNING:
|
25
|
+
# Calculation is in progress.
|
26
|
+
# Valid inputs are CANCEL
|
27
|
+
# COMPLETE:
|
28
|
+
# Calculation is completed
|
29
|
+
# No valid inputs
|
30
|
+
# ABORTED:
|
31
|
+
module AimsProject
|
32
|
+
|
33
|
+
class Calculation
|
34
|
+
|
35
|
+
# The name of the geometry file
|
36
|
+
attr_accessor :geometry
|
37
|
+
|
38
|
+
# The name of the control file
|
39
|
+
attr_accessor :control
|
40
|
+
|
41
|
+
# The current calculation status
|
42
|
+
attr_accessor :status
|
43
|
+
|
44
|
+
# The calculation subdirectory, (can be nil)
|
45
|
+
attr_accessor :calc_subdir
|
46
|
+
|
47
|
+
# An array of (#to_s) items that are the calculation history
|
48
|
+
attr_accessor :history
|
49
|
+
|
50
|
+
# Timestamp indicating creation of this calculation
|
51
|
+
attr_writer :created_at
|
52
|
+
def created_at
|
53
|
+
@created_at = cast_as_date(@created_at)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Timestamp indicating last update of this calculation
|
57
|
+
# (currently only updates when saved)
|
58
|
+
attr_writer :updated_at
|
59
|
+
def updated_at
|
60
|
+
# Cast value to Date
|
61
|
+
# Do this in the accessor because loading from YAML bypasses the setter method
|
62
|
+
@updated_at = cast_as_date(@updated_at)
|
63
|
+
end
|
64
|
+
|
65
|
+
def cast_as_date(obj)
|
66
|
+
if obj.is_a? Date or obj.is_a? Time or obj.is_a? DateTime
|
67
|
+
obj.to_datetime
|
68
|
+
elsif obj.is_a? String
|
69
|
+
DateTime.parse(obj)
|
70
|
+
# unless s
|
71
|
+
# s = DateTime.strptime(obj, "%F %T %z")
|
72
|
+
# end
|
73
|
+
# unless s
|
74
|
+
# s = DateTime.strptime(obj, "%FT%s%z")
|
75
|
+
# end
|
76
|
+
# s
|
77
|
+
else
|
78
|
+
DateTime.new(0)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Find all calculations in the current directory
|
83
|
+
# with a given status
|
84
|
+
def Calculation.find_all(status)
|
85
|
+
# Catch all root calculations
|
86
|
+
calculations = Dir.glob File.join(AimsProject::CALCULATION_DIR, "*", AimsProject::CALC_STATUS_FILENAME)
|
87
|
+
# Catch all sub calculations
|
88
|
+
calculations << Dir.glob(File.join(AimsProject::CALCULATION_DIR, "*", "*", AimsProject::CALC_STATUS_FILENAME))
|
89
|
+
calculations.collect{|calc_status_file|
|
90
|
+
calc_dir = File.dirname(calc_status_file)
|
91
|
+
calc = Calculation.load(calc_dir)
|
92
|
+
if (status == calc.status)
|
93
|
+
calc
|
94
|
+
else
|
95
|
+
nil
|
96
|
+
end
|
97
|
+
}.compact
|
98
|
+
end
|
99
|
+
|
100
|
+
# Load a calculation from the serialized yaml file in the given directory
|
101
|
+
# raises an *ObjectFileNotFoundException* if the specified directory does
|
102
|
+
# not contain a yaml serialization of the calculation.
|
103
|
+
# raises a *CorruptObjectFileException* if the yaml file cannot be de-serialized
|
104
|
+
def Calculation.load(dir)
|
105
|
+
|
106
|
+
calc_file = File.join(dir, AimsProject::CALC_STATUS_FILENAME)
|
107
|
+
raise ObjectFileNotFoundException.new(calc_file) unless File.exists?(calc_file)
|
108
|
+
|
109
|
+
f = File.open(calc_file, 'r')
|
110
|
+
calc_obj = YAML.load(f)
|
111
|
+
f.close
|
112
|
+
|
113
|
+
raise CorruptObjectFileException.new(calc_file) unless calc_obj
|
114
|
+
return calc_obj
|
115
|
+
end
|
116
|
+
|
117
|
+
# Create a calculation and the corresponding
|
118
|
+
# directory structure given a geometry and a control
|
119
|
+
# This method will search for the files geometry.#{geometry}.in
|
120
|
+
# and control.#{control}.in in the project directory, then
|
121
|
+
# create a calculation directory that is the merger of those two
|
122
|
+
# filenames, and finally copy the geometry and control files into
|
123
|
+
# the calculation directory and rename them geometry.in and control.in
|
124
|
+
# @param [String] geometry The filename of the geometry file to use to initialize the calculation
|
125
|
+
# @param [String] control The filename of the control file to use to initialize the calculation
|
126
|
+
# @param [Hash<Symbol, Object>] user_vars A symbol=>Object hash of variables that will be available when
|
127
|
+
# evaluating the geometry and control files using embedded ruby
|
128
|
+
# This hash is also used to generate a calculation subdirectory
|
129
|
+
def Calculation.create(project, geometry, control, user_vars = {})
|
130
|
+
|
131
|
+
calc = Calculation.new(geometry, control)
|
132
|
+
calc.created_at = Time.new
|
133
|
+
|
134
|
+
control_in = calc.control_file
|
135
|
+
geometry_in = calc.geometry_file
|
136
|
+
|
137
|
+
# Define the calculation sub-directory if user_vars exists
|
138
|
+
unless user_vars.empty?
|
139
|
+
calc.calc_subdir = user_vars.keys.collect{|k| (k.to_s + "=" + user_vars[k].to_s).gsub('@', '').gsub(' ','_') }.join("..")
|
140
|
+
end
|
141
|
+
|
142
|
+
# Add configuration variables to the calculation binding
|
143
|
+
uvars_file = File.join(AimsProject::CONFIG_DIR, "user_variables.rb")
|
144
|
+
calc.get_binding.eval(File.read(uvars_file)) if File.exists?(uvars_file)
|
145
|
+
|
146
|
+
# Merge project variables into calcuation binding
|
147
|
+
if project
|
148
|
+
project.instance_variables.each{|v|
|
149
|
+
if v == :@name # Ignore the project name
|
150
|
+
calc.instance_variable_set(:@project_name, project.instance_variable_get(v))
|
151
|
+
else
|
152
|
+
calc.instance_variable_set(v, project.instance_variable_get(v))
|
153
|
+
end
|
154
|
+
}
|
155
|
+
end
|
156
|
+
|
157
|
+
# Merge user-vars to the calculation binding
|
158
|
+
user_vars.each_pair{|sym, val|
|
159
|
+
calc.instance_variable_set(sym, val)
|
160
|
+
}
|
161
|
+
|
162
|
+
|
163
|
+
# Check file existence
|
164
|
+
raise "Unable to locate #{control_in}" unless File.exists?(control_in)
|
165
|
+
raise "Unable to locate #{geometry_in}" unless File.exists?(geometry_in)
|
166
|
+
|
167
|
+
# Validate the files
|
168
|
+
raise "#{geometry_in} has changed since last use" unless check_version(geometry_in)
|
169
|
+
raise "#{control_in} has changed since last use" unless check_version(control_in)
|
170
|
+
|
171
|
+
# Validate that the directory doesn't already exist
|
172
|
+
if Dir.exists? calc.calculation_directory
|
173
|
+
raise "Could not create calculation.\n #{calc.calculation_directory} already exists. \n\n If you really want to re-create this calculation, then manually delete it and try again. \n"
|
174
|
+
end
|
175
|
+
FileUtils.mkdir_p calc.calculation_directory
|
176
|
+
|
177
|
+
erb = ERB.new(File.read(control_in))
|
178
|
+
File.open File.join(calc.calculation_directory, "control.in"), "w" do |f|
|
179
|
+
f.puts erb.result(calc.get_binding)
|
180
|
+
end
|
181
|
+
|
182
|
+
erb = ERB.new(File.read(geometry_in))
|
183
|
+
File.open File.join(calc.calculation_directory, "geometry.in"), "w" do |f|
|
184
|
+
f.puts erb.result(calc.get_binding)
|
185
|
+
end
|
186
|
+
|
187
|
+
|
188
|
+
calc.status = AimsProject::STAGED
|
189
|
+
calc.save
|
190
|
+
|
191
|
+
return calc
|
192
|
+
end
|
193
|
+
|
194
|
+
# Get the binding for this calculation
|
195
|
+
def get_binding
|
196
|
+
binding()
|
197
|
+
end
|
198
|
+
|
199
|
+
# The name of this calculation
|
200
|
+
def name
|
201
|
+
"#{geometry}.#{control}"
|
202
|
+
end
|
203
|
+
|
204
|
+
# Intended to replace @geometry, but needs lots of regression testing
|
205
|
+
# for now, will just generate this on the fly
|
206
|
+
def input_geometry
|
207
|
+
unless @actual_geometry
|
208
|
+
@input_geometry = Aims::GeometryParser.parse(File.join(self.calculation_directory, "geometry.in"))
|
209
|
+
end
|
210
|
+
@input_geometry
|
211
|
+
end
|
212
|
+
|
213
|
+
# Set the status to HOLD.
|
214
|
+
# Only possible if status is currently STAGED
|
215
|
+
def hold
|
216
|
+
if STAGED == status
|
217
|
+
self.status = HOLD
|
218
|
+
save
|
219
|
+
return true
|
220
|
+
else
|
221
|
+
return false
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
# Set the status to STAGED if current status is HOLD
|
226
|
+
def release
|
227
|
+
if HOLD == status
|
228
|
+
self.status = STAGED
|
229
|
+
save
|
230
|
+
return true
|
231
|
+
else
|
232
|
+
return false
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
# Create a new calculation that will restart a geometry relaxation
|
237
|
+
# calculation using the last available geometry.
|
238
|
+
# This method will generate a new file in
|
239
|
+
# the geometry directory with the extension +restartN+, where
|
240
|
+
# N will be incremented if the filename already exists.
|
241
|
+
# A new calculation will be created with the new geometry and
|
242
|
+
# the original control.
|
243
|
+
def restart_relaxation
|
244
|
+
|
245
|
+
# Create a new geometry file
|
246
|
+
geometry_orig = self.geometry_file
|
247
|
+
|
248
|
+
# Append the subdirectory if a subdir
|
249
|
+
if @calc_subdir
|
250
|
+
geometry_orig = geometry_orig + ".#{calc_subdir}"
|
251
|
+
end
|
252
|
+
|
253
|
+
# If restarting a restart, then increment the digit
|
254
|
+
if (geometry_orig.split(".").last =~ /restart(\d+)/)
|
255
|
+
n = $1.to_i
|
256
|
+
geometry_orig = geometry_orig.split(".")[0...-1].join(".")
|
257
|
+
else
|
258
|
+
n = 0
|
259
|
+
end
|
260
|
+
|
261
|
+
begin
|
262
|
+
n += 1
|
263
|
+
geometry_new = geometry_orig + ".restart#{n}"
|
264
|
+
end while File.exist?(geometry_new)
|
265
|
+
|
266
|
+
File.open(geometry_new, 'w') do |f|
|
267
|
+
f.puts "# Final geometry from #{calculation_directory}"
|
268
|
+
f.puts self.geometry_next_step.format_geometry_in
|
269
|
+
end
|
270
|
+
|
271
|
+
Calculation.create(nil, geometry_new, self.control)
|
272
|
+
|
273
|
+
end
|
274
|
+
|
275
|
+
# Initialize a new calculation. Consider using Calculation#create
|
276
|
+
# to generate the directory structure as well.
|
277
|
+
# @param [String] geometry the filename of the input geometry
|
278
|
+
# @param [String] control the filename of the input control
|
279
|
+
def initialize(geometry, control)
|
280
|
+
self.geometry = File.basename(geometry)
|
281
|
+
self.control = File.basename(control)
|
282
|
+
self.history = Array.new
|
283
|
+
end
|
284
|
+
|
285
|
+
# Serialize this calculation as a yaml file
|
286
|
+
def save(dir = nil)
|
287
|
+
self.updated_at = Time.new
|
288
|
+
dir = calculation_directory unless dir
|
289
|
+
File.open(File.join(dir, AimsProject::CALC_STATUS_FILENAME), 'w') do |f|
|
290
|
+
f.puts YAML.dump(self)
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
# Reload this calculation from the serialized YAML file
|
295
|
+
def reload
|
296
|
+
c = Calculation.load(self.calculation_directory)
|
297
|
+
self.geometry = c.geometry
|
298
|
+
@input_geometry = c.input_geometry
|
299
|
+
self.control = c.control
|
300
|
+
self.status = c.status
|
301
|
+
return self
|
302
|
+
end
|
303
|
+
|
304
|
+
# Determine the name of the control.in file from the
|
305
|
+
# control variable.
|
306
|
+
def control_file
|
307
|
+
File.join(AimsProject::CONTROL_DIR, self.control)
|
308
|
+
end
|
309
|
+
|
310
|
+
# Determine the name of the geometr.in file from the
|
311
|
+
# geometry variable
|
312
|
+
def geometry_file
|
313
|
+
File.join(AimsProject::GEOMETRY_DIR, self.geometry)
|
314
|
+
end
|
315
|
+
|
316
|
+
#
|
317
|
+
# Check for the existence of a cached version of the input file
|
318
|
+
# If it exists, check if the cached version is the same
|
319
|
+
# as the working version, and return true if they are, false if they are not.
|
320
|
+
# If the cached version does not exist, then cache the working version and return true.
|
321
|
+
def Calculation.check_version(file)
|
322
|
+
cache_dir = ".input_cache"
|
323
|
+
unless File.exists? cache_dir
|
324
|
+
Dir.mkdir cache_dir
|
325
|
+
Dir.mkdir File.join(cache_dir, AimsProject::GEOMETRY_DIR)
|
326
|
+
Dir.mkdir File.join(cache_dir, AimsProject::CONTROL_DIR)
|
327
|
+
end
|
328
|
+
|
329
|
+
return false unless File.exists?(file)
|
330
|
+
cache_version = File.join(cache_dir, file)
|
331
|
+
if File.exists?(cache_version)
|
332
|
+
return FileUtils.compare_file(file, cache_version)
|
333
|
+
else
|
334
|
+
FileUtils.cp_r file, cache_version
|
335
|
+
return true
|
336
|
+
end
|
337
|
+
|
338
|
+
end
|
339
|
+
|
340
|
+
# The path of this calculation relative to the project
|
341
|
+
def relative_path
|
342
|
+
calculation_directory
|
343
|
+
end
|
344
|
+
|
345
|
+
#
|
346
|
+
# Return the directory for this calculation
|
347
|
+
#
|
348
|
+
def calculation_directory
|
349
|
+
File.join AimsProject::CALCULATION_DIR, self.name, (@calc_subdir || "")
|
350
|
+
end
|
351
|
+
|
352
|
+
def load_output(output_pattern = "*output*")
|
353
|
+
output_files = Dir.glob(File.join(calculation_directory, output_pattern))
|
354
|
+
if output_files.empty?
|
355
|
+
@output = nil
|
356
|
+
else
|
357
|
+
@output = Aims::OutputParser.parse(output_files.last)
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
# Search the calculation directory for the calculation output.
|
362
|
+
# If found, parse it and return the Aims::AimsOutput object, otherwise
|
363
|
+
# return nil.
|
364
|
+
# If multiple output files are found, use the last one in the list
|
365
|
+
# when sorted alpha-numerically. (This is assumed to be the most recent calculation)
|
366
|
+
def output(output_pattern = "*output*")
|
367
|
+
unless @output
|
368
|
+
load_output(output_pattern)
|
369
|
+
end
|
370
|
+
@output
|
371
|
+
end
|
372
|
+
|
373
|
+
# Parse the calculation output and return the final geometry
|
374
|
+
# of this calculation. Return nil if no output is found.
|
375
|
+
def final_geometry
|
376
|
+
# ouput is not cached, so we only retrieve it once
|
377
|
+
o = self.output
|
378
|
+
if o
|
379
|
+
o.final_geometry
|
380
|
+
else
|
381
|
+
nil
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
# Parse the geometry.in.next_step file in the calculation directory
|
386
|
+
# if it exists and return the Aims::Geometry object or nil
|
387
|
+
def geometry_next_step
|
388
|
+
g_file = File.join(calculation_directory, "geometry.in.next_step")
|
389
|
+
if File.exists?(g_file)
|
390
|
+
Aims::GeometryParser.parse(g_file)
|
391
|
+
else
|
392
|
+
nil
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
# Return whether this calculation is converged or not
|
397
|
+
def converged?
|
398
|
+
if output.nil?
|
399
|
+
false
|
400
|
+
else
|
401
|
+
output.geometry_converged
|
402
|
+
end
|
403
|
+
end
|
404
|
+
|
405
|
+
end
|
406
|
+
end
|
@@ -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
|