digichem-core 6.0.0rc1__py3-none-any.whl
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.
- digichem/__init__.py +75 -0
- digichem/basis.py +116 -0
- digichem/config/README +3 -0
- digichem/config/__init__.py +5 -0
- digichem/config/base.py +321 -0
- digichem/config/locations.py +14 -0
- digichem/config/parse.py +90 -0
- digichem/config/util.py +117 -0
- digichem/data/README +4 -0
- digichem/data/batoms/COPYING +18 -0
- digichem/data/batoms/LICENSE +674 -0
- digichem/data/batoms/README +2 -0
- digichem/data/batoms/__init__.py +0 -0
- digichem/data/batoms/batoms-renderer.py +351 -0
- digichem/data/config/digichem.yaml +714 -0
- digichem/data/functionals.csv +15 -0
- digichem/data/solvents.csv +185 -0
- digichem/data/tachyon/COPYING.md +5 -0
- digichem/data/tachyon/LICENSE +30 -0
- digichem/data/tachyon/tachyon_LINUXAMD64 +0 -0
- digichem/data/vmd/common.tcl +468 -0
- digichem/data/vmd/generate_combined_orbital_images.tcl +70 -0
- digichem/data/vmd/generate_density_images.tcl +45 -0
- digichem/data/vmd/generate_dipole_images.tcl +68 -0
- digichem/data/vmd/generate_orbital_images.tcl +57 -0
- digichem/data/vmd/generate_spin_images.tcl +66 -0
- digichem/data/vmd/generate_structure_images.tcl +40 -0
- digichem/datas.py +14 -0
- digichem/exception/__init__.py +7 -0
- digichem/exception/base.py +133 -0
- digichem/exception/uncatchable.py +63 -0
- digichem/file/__init__.py +1 -0
- digichem/file/base.py +364 -0
- digichem/file/cube.py +284 -0
- digichem/file/fchk.py +94 -0
- digichem/file/prattle.py +277 -0
- digichem/file/types.py +97 -0
- digichem/image/__init__.py +6 -0
- digichem/image/base.py +113 -0
- digichem/image/excited_states.py +335 -0
- digichem/image/graph.py +293 -0
- digichem/image/orbitals.py +239 -0
- digichem/image/render.py +617 -0
- digichem/image/spectroscopy.py +797 -0
- digichem/image/structure.py +115 -0
- digichem/image/vmd.py +826 -0
- digichem/input/__init__.py +3 -0
- digichem/input/base.py +78 -0
- digichem/input/digichem_input.py +500 -0
- digichem/input/gaussian.py +140 -0
- digichem/log.py +179 -0
- digichem/memory.py +166 -0
- digichem/misc/__init__.py +4 -0
- digichem/misc/argparse.py +44 -0
- digichem/misc/base.py +61 -0
- digichem/misc/io.py +239 -0
- digichem/misc/layered_dict.py +285 -0
- digichem/misc/text.py +139 -0
- digichem/misc/time.py +73 -0
- digichem/parse/__init__.py +13 -0
- digichem/parse/base.py +220 -0
- digichem/parse/cclib.py +138 -0
- digichem/parse/dump.py +253 -0
- digichem/parse/gaussian.py +130 -0
- digichem/parse/orca.py +96 -0
- digichem/parse/turbomole.py +201 -0
- digichem/parse/util.py +523 -0
- digichem/result/__init__.py +6 -0
- digichem/result/alignment/AA.py +114 -0
- digichem/result/alignment/AAA.py +61 -0
- digichem/result/alignment/FAP.py +148 -0
- digichem/result/alignment/__init__.py +3 -0
- digichem/result/alignment/base.py +310 -0
- digichem/result/angle.py +153 -0
- digichem/result/atom.py +742 -0
- digichem/result/base.py +258 -0
- digichem/result/dipole_moment.py +332 -0
- digichem/result/emission.py +402 -0
- digichem/result/energy.py +323 -0
- digichem/result/excited_state.py +821 -0
- digichem/result/ground_state.py +94 -0
- digichem/result/metadata.py +644 -0
- digichem/result/multi.py +98 -0
- digichem/result/nmr.py +1086 -0
- digichem/result/orbital.py +647 -0
- digichem/result/result.py +244 -0
- digichem/result/soc.py +272 -0
- digichem/result/spectroscopy.py +514 -0
- digichem/result/tdm.py +267 -0
- digichem/result/vibration.py +167 -0
- digichem/test/__init__.py +6 -0
- digichem/test/conftest.py +4 -0
- digichem/test/test_basis.py +71 -0
- digichem/test/test_calculate.py +30 -0
- digichem/test/test_config.py +78 -0
- digichem/test/test_cube.py +369 -0
- digichem/test/test_exception.py +16 -0
- digichem/test/test_file.py +104 -0
- digichem/test/test_image.py +337 -0
- digichem/test/test_input.py +64 -0
- digichem/test/test_parsing.py +79 -0
- digichem/test/test_prattle.py +36 -0
- digichem/test/test_result.py +489 -0
- digichem/test/test_translate.py +112 -0
- digichem/test/util.py +207 -0
- digichem/translate.py +591 -0
- digichem_core-6.0.0rc1.dist-info/METADATA +96 -0
- digichem_core-6.0.0rc1.dist-info/RECORD +111 -0
- digichem_core-6.0.0rc1.dist-info/WHEEL +4 -0
- digichem_core-6.0.0rc1.dist-info/licenses/COPYING.md +10 -0
- digichem_core-6.0.0rc1.dist-info/licenses/LICENSE +11 -0
digichem/image/vmd.py
ADDED
|
@@ -0,0 +1,826 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
import subprocess
|
|
3
|
+
import math
|
|
4
|
+
from PIL import Image
|
|
5
|
+
from uuid import uuid4
|
|
6
|
+
import os
|
|
7
|
+
from math import fabs
|
|
8
|
+
import shutil
|
|
9
|
+
|
|
10
|
+
from digichem.exception.base import File_maker_exception
|
|
11
|
+
from digichem.image.render import Render_maker
|
|
12
|
+
import digichem.log
|
|
13
|
+
from digichem.datas import get_resource
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class VMD_image_maker(Render_maker):
|
|
17
|
+
"""
|
|
18
|
+
Class for generating image files from Gaussian outputs using VMD.
|
|
19
|
+
|
|
20
|
+
Nearly all of the work here is done by other programs, primarily VMD (https://www.ks.uiuc.edu/Research/vmd/). These classes are mostly just wrappers.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
# The name of the vmd/tcl script (relative to the vmd script folder) used to render images. Inheriting classes should set this to an appropriate script file.
|
|
24
|
+
vmd_script = ""
|
|
25
|
+
# The filename extension to use for molecule scene files produced by vmd.
|
|
26
|
+
scene_file_extension = ".scene"
|
|
27
|
+
# 'Path' to the vmd executable.
|
|
28
|
+
#vmd_executable = "vmd"
|
|
29
|
+
# 'Path' to the tachyon executable.
|
|
30
|
+
#tachyon_executable = "tachyon"
|
|
31
|
+
# The initial resolution at which an image is rendered. This test image will then be discarded once relevant info has been extracted.
|
|
32
|
+
test_resolution = 300
|
|
33
|
+
|
|
34
|
+
# Name of the section where we get some specific configs.
|
|
35
|
+
options_name = "orbital"
|
|
36
|
+
|
|
37
|
+
def __init__(self, *args, cube_file = None, rotations = None, auto_crop = True, rendering_style = "pastel", resolution = 1024, also_make_png = True, isovalue = 0.2,
|
|
38
|
+
vmd_executable = "vmd", tachyon_executable = "tachyon", vmd_logging = False,
|
|
39
|
+
**kwargs):
|
|
40
|
+
"""
|
|
41
|
+
Constructor for Image_maker objects.
|
|
42
|
+
|
|
43
|
+
:param output: The path to write image files to. As more than one image can be created by this class, this name is used as the base name and is modified for each image. The suffix will be honoured (rendered imges will use it as a hint for their output format.
|
|
44
|
+
:param cube_file: The path to a cube_file to use to render images.
|
|
45
|
+
:param rotations: A list of tuples of rotations, where the first index in the tuple specifies the axis to rotate about and the second is the angle to rotate (in radians).
|
|
46
|
+
:param auto_crop: If False, images will not have excess white space cropped.
|
|
47
|
+
:param rendering_style: A string describing the rendering style to use, either 'digichem' or 'gaussian'.
|
|
48
|
+
:param resolution: The max width or height of the rendered images in pixels.
|
|
49
|
+
:param also_make_png: If True, additional images will be rendered in PNG format. This option is useful to generate higher quality images alongside more portable formats. If 'output' is a .png file, then it is wise to set this option to False (otherwise two png files will be rendered, which is a waste).
|
|
50
|
+
:param isovalue: The isovalue to use for rendering isosurfaces. Has no effect when rendering only atoms.
|
|
51
|
+
:param vmd_executable: 'Path' to the vmd executable to use for image rendering. Defaults to relying on the command 'vmd'.
|
|
52
|
+
:param tachyon_executable: 'Path' to the tachyon executable to use for image rendering. Defaults to relying on the command 'tachyon'.
|
|
53
|
+
:param vmd_logging: Whether to print output from vmd.
|
|
54
|
+
"""
|
|
55
|
+
super().__init__(
|
|
56
|
+
*args,
|
|
57
|
+
cube_file = cube_file,
|
|
58
|
+
rotations = rotations,
|
|
59
|
+
auto_crop = auto_crop,
|
|
60
|
+
resolution = resolution,
|
|
61
|
+
also_make_png = also_make_png,
|
|
62
|
+
isovalue = isovalue,
|
|
63
|
+
**kwargs
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# Some options that control how we function.
|
|
67
|
+
self.rendering_style = rendering_style
|
|
68
|
+
|
|
69
|
+
# Save executable paths.
|
|
70
|
+
self.vmd_executable = vmd_executable
|
|
71
|
+
self.tachyon_executable = tachyon_executable
|
|
72
|
+
|
|
73
|
+
self.vmd_logging = vmd_logging
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
if self.rendering_style is None:
|
|
77
|
+
# Panic time.
|
|
78
|
+
raise ValueError("rendering_style must not be None")
|
|
79
|
+
|
|
80
|
+
# The colours we will be using for rendering (depends on rendering_stype and is only for reference; the actual definition of each style is in VMD).
|
|
81
|
+
if self.rendering_style == "gaussian":
|
|
82
|
+
self.primary_colour = "green"
|
|
83
|
+
self.secondary_colour = "red"
|
|
84
|
+
elif self.rendering_style == "vesta":
|
|
85
|
+
self.primary_colour = "blue"
|
|
86
|
+
self.secondary_colour = "yellow"
|
|
87
|
+
else:
|
|
88
|
+
self.primary_colour = "blue"
|
|
89
|
+
self.secondary_colour = "red"
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def rotations(self):
|
|
93
|
+
# Digichem rotates the wrong way round for some reason, reverse for our rendering engine.
|
|
94
|
+
# VMD also likes degrees not radians.
|
|
95
|
+
return [(axis, math.degrees(-theta)) for axis, theta in self._rotations]
|
|
96
|
+
|
|
97
|
+
@classmethod
|
|
98
|
+
def from_options(self, output, *, cube_file = None, rotations = None, options, **kwargs):
|
|
99
|
+
"""
|
|
100
|
+
Constructor that takes a dictionary of config like options.
|
|
101
|
+
"""
|
|
102
|
+
return self(
|
|
103
|
+
output,
|
|
104
|
+
cube_file = cube_file,
|
|
105
|
+
rotations = rotations,
|
|
106
|
+
auto_crop = options['render']['auto_crop'],
|
|
107
|
+
rendering_style = options['render']['vmd']['rendering_style'],
|
|
108
|
+
resolution = options['render']['resolution'],
|
|
109
|
+
isovalue = options['render'][self.options_name]['isovalue'],
|
|
110
|
+
use_existing = options['render']['use_existing'],
|
|
111
|
+
dont_modify = not options['render']['enable_rendering'],
|
|
112
|
+
vmd_executable = options['render']['vmd']['executable'],
|
|
113
|
+
tachyon_executable = options['render']['vmd']['tachyon'],
|
|
114
|
+
vmd_logging = options['logging']['render_logging'],
|
|
115
|
+
**kwargs
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
@classmethod
|
|
119
|
+
def safe_name(self, file_name):
|
|
120
|
+
"""
|
|
121
|
+
Get a filename free of 'unusual' characters.
|
|
122
|
+
:return: The safe path name.
|
|
123
|
+
"""
|
|
124
|
+
safe_chars = "._"
|
|
125
|
+
return "".join([char if char.isalnum() or char in safe_chars else "_" for char in file_name])
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
def vmd_script_path(self):
|
|
129
|
+
"""
|
|
130
|
+
Get the file path to the VMD script to be used by this class to render images (as a pathlib Path).
|
|
131
|
+
"""
|
|
132
|
+
return get_resource('data/vmd/{}'.format(self.vmd_script))
|
|
133
|
+
|
|
134
|
+
@property
|
|
135
|
+
def tcl_common_path(self):
|
|
136
|
+
"""
|
|
137
|
+
Get the file path to the Tcl script that is common to all of our other Tcl scripts (as a pathlib Path).
|
|
138
|
+
"""
|
|
139
|
+
return get_resource('data/vmd/common.tcl')
|
|
140
|
+
|
|
141
|
+
@property
|
|
142
|
+
def creation_message(self):
|
|
143
|
+
"""
|
|
144
|
+
A short message that may (depending on log-level) be printed to the user before make_files() is called.
|
|
145
|
+
"""
|
|
146
|
+
return "Rendering '{}' to file(s)".format(self.output if self.full_path_names else self.output.name)
|
|
147
|
+
|
|
148
|
+
def make_files(self):
|
|
149
|
+
"""
|
|
150
|
+
Make the image files referenced by this object.
|
|
151
|
+
|
|
152
|
+
The new image will be written to file.
|
|
153
|
+
"""
|
|
154
|
+
# Run VMD, which writes a plain text description of our scene to file.
|
|
155
|
+
self.run_VMD_script()
|
|
156
|
+
|
|
157
|
+
# Next run tachyon for each of our 4 scene files, which creates tga files.
|
|
158
|
+
for image_name in ['x0y0z0', 'x90y0z0', 'x0y90z0', 'x45y45z45']:
|
|
159
|
+
image_path = self.file_path[image_name]
|
|
160
|
+
try:
|
|
161
|
+
# First we'll render a test image at a lower resolution. We'll then crop it, and use the decrease in final resolution to know how much bigger we need to render in our final image to hit our target resolution.
|
|
162
|
+
# Unless of course auto_crop is False, in which case we use our target resolution immediately.
|
|
163
|
+
resolution = self.test_resolution if self.auto_crop else self.target_resolution
|
|
164
|
+
self.run_tachyon_renderer(
|
|
165
|
+
image_path.with_name(self.safe_name(image_path.with_suffix(self.scene_file_extension).name)),
|
|
166
|
+
image_path.with_suffix(".tga"),
|
|
167
|
+
resolution
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
if self.auto_crop:
|
|
171
|
+
# Load the test image and autocrop it.
|
|
172
|
+
with Image.open(image_path.with_suffix(".tga"), "r") as test_im:
|
|
173
|
+
small_test_im = self.auto_crop_image(test_im)
|
|
174
|
+
|
|
175
|
+
# Get the cropped size. We're interested in the largest dimension, as this is what we'll output as.
|
|
176
|
+
cropped_resolution = max(small_test_im.size)
|
|
177
|
+
|
|
178
|
+
# From this we can work out the ratio between our true resolution and the resolution we've been asked for.
|
|
179
|
+
resolution_ratio = cropped_resolution / self.test_resolution
|
|
180
|
+
|
|
181
|
+
# Now we can re-render asking for a larger resolution so that when we crop again, we should get our target resolution.
|
|
182
|
+
# Note that resolution ratio is a fraction, so self.target_resolution / resolution_ratio results in an increase.
|
|
183
|
+
self.run_tachyon_renderer(
|
|
184
|
+
image_path.with_name(self.safe_name(image_path.with_suffix(self.scene_file_extension).name)),
|
|
185
|
+
image_path.with_suffix(".tga"),
|
|
186
|
+
self.target_resolution / resolution_ratio
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
# Delete the now unneeded scene file.
|
|
190
|
+
os.remove(image_path.with_name(self.safe_name(image_path.with_suffix(self.scene_file_extension).name)))
|
|
191
|
+
except Exception as e:
|
|
192
|
+
raise File_maker_exception(self, "Error in tachyon rendering") from e
|
|
193
|
+
|
|
194
|
+
# Convert to a better set of formats.
|
|
195
|
+
# Open the file we just rendered.
|
|
196
|
+
with Image.open(image_path.with_suffix(".tga"), "r") as im:
|
|
197
|
+
|
|
198
|
+
# If we've been asked to autocrop, do so.
|
|
199
|
+
if self.auto_crop:
|
|
200
|
+
try:
|
|
201
|
+
cropped_image = self.auto_crop_image(im)
|
|
202
|
+
except Exception:
|
|
203
|
+
raise File_maker_exception(self, "Error in post-rendering auto-crop")
|
|
204
|
+
else:
|
|
205
|
+
cropped_image = im
|
|
206
|
+
|
|
207
|
+
# Now save in our main output format.
|
|
208
|
+
cropped_image.save(image_path)
|
|
209
|
+
|
|
210
|
+
# And also as a higher quality png if we've been asked to.
|
|
211
|
+
if self.also_make_png:
|
|
212
|
+
cropped_image.save(self.file_path[image_name + "_big"])
|
|
213
|
+
|
|
214
|
+
# And delete the old .tga (unless we've just rendered to .tga!).
|
|
215
|
+
if image_path.suffix != ".tga":
|
|
216
|
+
os.remove(image_path.with_suffix(".tga"))
|
|
217
|
+
|
|
218
|
+
@property
|
|
219
|
+
def prepared_translations(self):
|
|
220
|
+
"""
|
|
221
|
+
Our list of translations in a from ready for VMD/Tcl.
|
|
222
|
+
"""
|
|
223
|
+
return "{},{},{}".format(self.translations[0], self.translations[1], self.translations[2])
|
|
224
|
+
|
|
225
|
+
@property
|
|
226
|
+
def prepared_rotations(self):
|
|
227
|
+
"""
|
|
228
|
+
Our list of rotations in a form ready for VMD/Tcl.
|
|
229
|
+
"""
|
|
230
|
+
# This is a stringified form of our list of tuples that we'll pass to VMD (or rather the TCL interpreter).
|
|
231
|
+
if len(self.rotations) > 0:
|
|
232
|
+
rot_string = ":".join(["{},{}".format(axis, angle) for axis, angle in self.rotations])
|
|
233
|
+
else:
|
|
234
|
+
# An empty string will be misinterpreted on the command line.
|
|
235
|
+
rot_string = "0,0"
|
|
236
|
+
return rot_string
|
|
237
|
+
|
|
238
|
+
@property
|
|
239
|
+
def VMD_signature(self):
|
|
240
|
+
"""
|
|
241
|
+
The signature pass to subprocess.run used to call VMD. Inheriting classes should write their own implementation.
|
|
242
|
+
"""
|
|
243
|
+
return ""
|
|
244
|
+
|
|
245
|
+
@property
|
|
246
|
+
def inputs(self):
|
|
247
|
+
"""
|
|
248
|
+
A dictionary of all the input files required by VMD for this image.
|
|
249
|
+
|
|
250
|
+
inputs is a dictionary where each key is the path to the locally accessible file,
|
|
251
|
+
and each value is the true location.
|
|
252
|
+
"""
|
|
253
|
+
working_directory = self.output.parent
|
|
254
|
+
return {
|
|
255
|
+
Path(working_directory, self.vmd_script_path.name): self.vmd_script_path,
|
|
256
|
+
Path(working_directory, self.tcl_common_path.name): self.tcl_common_path,
|
|
257
|
+
Path(working_directory, self.safe_name(Path(str(self.input_file)).name)): Path(str(self.input_file)).absolute()
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
def run_VMD_script(self):
|
|
261
|
+
"""
|
|
262
|
+
Called as part of the make() method, inheriting classes can implement this method if they have a different VMD call signature.
|
|
263
|
+
|
|
264
|
+
Also see VMD_signature, which may do what you need.
|
|
265
|
+
|
|
266
|
+
:return: The CompletedProcess object.
|
|
267
|
+
"""
|
|
268
|
+
# Setting these variables greatly improves VMD startup time.
|
|
269
|
+
os.environ["VMDNOOPTIX"] = "1"
|
|
270
|
+
os.environ["VMDNOOSPRAY"] = "1"
|
|
271
|
+
|
|
272
|
+
# VMD has two run scripts, one in C-shell (which is obviously garbage) and one in bash.
|
|
273
|
+
# Sadly, C-Shell is often the default, and it's doesn't support filenames properly.
|
|
274
|
+
# We'll use the usual workaround of changing the working directory and using relative file names.
|
|
275
|
+
working_directory = self.output.parent
|
|
276
|
+
|
|
277
|
+
try:
|
|
278
|
+
# Create local links to our input files.
|
|
279
|
+
for input_dst, input_src in self.inputs.items():
|
|
280
|
+
# Don't symlink if the source is already in the output dir.
|
|
281
|
+
if input_dst.absolute() != input_src.absolute():
|
|
282
|
+
try:
|
|
283
|
+
os.symlink(input_src, input_dst)
|
|
284
|
+
|
|
285
|
+
except Exception:
|
|
286
|
+
# Couldn't symlink for some reason, print a warning and copy instead.
|
|
287
|
+
digichem.log.get_logger().warning("Failed to create symlink for VMD input file, falling back to copy (this may take a while)")
|
|
288
|
+
shutil.copy(input_src, input_dst)
|
|
289
|
+
|
|
290
|
+
digichem.log.get_logger().debug(str(self.VMD_signature))
|
|
291
|
+
# Run VMD, which renders our image for us.
|
|
292
|
+
try:
|
|
293
|
+
return subprocess.run(
|
|
294
|
+
self.VMD_signature,
|
|
295
|
+
# We normally capture and discard stdout (because VMD is VERY verbose), but if we're at a suitable log level, we'll print it.
|
|
296
|
+
# Nothing useful appears to be printed to stderr, so we'll treat it the same as stdout.,
|
|
297
|
+
stdin = subprocess.DEVNULL,
|
|
298
|
+
stdout = subprocess.DEVNULL if not self.vmd_logging else None,
|
|
299
|
+
stderr = subprocess.STDOUT,
|
|
300
|
+
universal_newlines = True,
|
|
301
|
+
cwd = working_directory,
|
|
302
|
+
# VMD has a tendency to sigsegv when closing with VMDNOOPTIX set to on (even tho everything is fine) so we can't check retval sadly.
|
|
303
|
+
#check = True
|
|
304
|
+
)
|
|
305
|
+
except FileNotFoundError:
|
|
306
|
+
raise File_maker_exception(self, "Could not locate vmd executable '{}'".format(self.vmd_executable))
|
|
307
|
+
|
|
308
|
+
finally:
|
|
309
|
+
# Clean up.
|
|
310
|
+
# Remove the copied inputs
|
|
311
|
+
for input_dst, input_src in self.inputs.items():
|
|
312
|
+
# Don't symlink if the source is already in the output dir.
|
|
313
|
+
if input_dst.absolute() != input_src.absolute():
|
|
314
|
+
try:
|
|
315
|
+
input_dst.unlink()
|
|
316
|
+
|
|
317
|
+
except Exception:
|
|
318
|
+
# Warnings are useful here, if we can't delete the files we probably failed to copy them in the first place.
|
|
319
|
+
digichem.log.get_logger().warning("Failed to delete VMD input file '{}'".format(input_dst), exc_info = True)
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def run_tachyon_renderer(self, scene_file, tga_file, resolution):
|
|
323
|
+
"""
|
|
324
|
+
Called as part of the make() method.
|
|
325
|
+
|
|
326
|
+
Take a molecule 'scene' file produced by VMD and render to an image with tachyon.
|
|
327
|
+
|
|
328
|
+
:param scene_file: A VMD scene file to read in as input.
|
|
329
|
+
:param tga_file: The filename to write to.
|
|
330
|
+
:param resolution: The resolution to render with.
|
|
331
|
+
"""
|
|
332
|
+
# Sadly, tachyon is old and decrepit and doesn't parse the output filename correctly (but weirdly the input file is fine).
|
|
333
|
+
# To get around this, we'll use a semi-random output name that we know is safe, and change-directory to the output directory immediately before calling tachyon.
|
|
334
|
+
# This way we can ensure there will be no nasty file-names for tachyon to get stuck on.
|
|
335
|
+
working_directory = tga_file.parent
|
|
336
|
+
tmpfile_name = ".tachyon_output_" + uuid4().hex + ".tga"
|
|
337
|
+
tmpfile_full_path = Path(tga_file.parent, tmpfile_name)
|
|
338
|
+
tachyon_process = None
|
|
339
|
+
try:
|
|
340
|
+
try:
|
|
341
|
+
# Now we can run tachyon.
|
|
342
|
+
tachyon_process = subprocess.run(
|
|
343
|
+
[
|
|
344
|
+
"{}".format(self.tachyon_executable),
|
|
345
|
+
scene_file.relative_to(working_directory),
|
|
346
|
+
"-aasamples", "12",
|
|
347
|
+
"-res", "{}".format(resolution), "{}".format(resolution),
|
|
348
|
+
"-o", tmpfile_name
|
|
349
|
+
],
|
|
350
|
+
stdout = subprocess.PIPE if not self.vmd_logging else None,
|
|
351
|
+
stderr = subprocess.STDOUT,
|
|
352
|
+
universal_newlines = True,
|
|
353
|
+
check = True,
|
|
354
|
+
cwd = working_directory
|
|
355
|
+
)
|
|
356
|
+
except FileNotFoundError:
|
|
357
|
+
# THINK this can only occur if the command itself cannot be found, but I might be wrong.
|
|
358
|
+
raise File_maker_exception(self, "Could not locate tachyon executable '{}'".format(self.tachyon_executable))
|
|
359
|
+
|
|
360
|
+
# Render complete (sadly we don't appear to have any ability to check for errors).
|
|
361
|
+
# Now rename our tmpfile.
|
|
362
|
+
#os.rename(tmpfile_full_path, tga_file)
|
|
363
|
+
tmpfile_full_path.rename(tga_file)
|
|
364
|
+
|
|
365
|
+
except Exception:
|
|
366
|
+
# Something went wrong, remove our tmpfile.
|
|
367
|
+
try:
|
|
368
|
+
os.remove(tmpfile_full_path)
|
|
369
|
+
except Exception:
|
|
370
|
+
pass
|
|
371
|
+
|
|
372
|
+
# If we didn't already show output, dump it now.
|
|
373
|
+
if not self.vmd_logging and tachyon_process is not None:
|
|
374
|
+
digichem.log.get_logger().error("Tachyon did not exit successfully, dumping output:\n{}".format(tachyon_process.stdout))
|
|
375
|
+
|
|
376
|
+
raise
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
class Structure_image_maker(VMD_image_maker):
|
|
380
|
+
"""
|
|
381
|
+
Class for creating structure images.
|
|
382
|
+
"""
|
|
383
|
+
|
|
384
|
+
vmd_script ="generate_structure_images.tcl"
|
|
385
|
+
|
|
386
|
+
@property
|
|
387
|
+
def VMD_signature(self):
|
|
388
|
+
"""
|
|
389
|
+
The arguments which we'll pass to VMD, inheriting classes can implement this method if they have a different VMD call signature.
|
|
390
|
+
"""
|
|
391
|
+
return [
|
|
392
|
+
"{}".format(self.vmd_executable),
|
|
393
|
+
"-dispdev", "none",
|
|
394
|
+
"-e", "{}".format(self.vmd_script_path.name),
|
|
395
|
+
"-args",
|
|
396
|
+
"{}".format(self.safe_name(Path(str(self.input_file)).name)),
|
|
397
|
+
"{}".format(self.tcl_common_path.name),
|
|
398
|
+
"{}".format(self.rendering_style),
|
|
399
|
+
"{}".format(self.prepared_translations),
|
|
400
|
+
"{}".format(self.prepared_rotations),
|
|
401
|
+
"{}".format(self.safe_name(self.file_path['x0y0z0'].with_suffix(self.scene_file_extension).name)),
|
|
402
|
+
"{}".format(self.safe_name(self.file_path['x90y0z0'].with_suffix(self.scene_file_extension).name)),
|
|
403
|
+
"{}".format(self.safe_name(self.file_path['x0y90z0'].with_suffix(self.scene_file_extension).name)),
|
|
404
|
+
"{}".format(self.safe_name(self.file_path['x45y45z45'].with_suffix(self.scene_file_extension).name))
|
|
405
|
+
]
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
class Orbital_image_maker(Structure_image_maker):
|
|
409
|
+
"""
|
|
410
|
+
Class for creating orbital images.
|
|
411
|
+
"""
|
|
412
|
+
|
|
413
|
+
vmd_script ="generate_orbital_images.tcl"
|
|
414
|
+
|
|
415
|
+
@property
|
|
416
|
+
def VMD_signature(self):
|
|
417
|
+
"""
|
|
418
|
+
The arguments which we'll pass to VMD, inheriting classes can implement this method if they have a different VMD call signature.
|
|
419
|
+
"""
|
|
420
|
+
return [
|
|
421
|
+
"{}".format(self.vmd_executable),
|
|
422
|
+
"-dispdev", "none",
|
|
423
|
+
"-e", "{}".format(self.vmd_script_path.name),
|
|
424
|
+
"-args",
|
|
425
|
+
"{}".format(self.safe_name(Path(str(self.input_file)).name)),
|
|
426
|
+
"{}".format(self.tcl_common_path.name),
|
|
427
|
+
"{}".format(self.rendering_style),
|
|
428
|
+
"{}".format(self.isovalue),
|
|
429
|
+
"{}".format(self.prepared_translations),
|
|
430
|
+
"{}".format(self.prepared_rotations),
|
|
431
|
+
"{}".format(self.safe_name(self.file_path['x0y0z0'].with_suffix(self.scene_file_extension).name)),
|
|
432
|
+
"{}".format(self.safe_name(self.file_path['x90y0z0'].with_suffix(self.scene_file_extension).name)),
|
|
433
|
+
"{}".format(self.safe_name(self.file_path['x0y90z0'].with_suffix(self.scene_file_extension).name)),
|
|
434
|
+
"{}".format(self.safe_name(self.file_path['x45y45z45'].with_suffix(self.scene_file_extension).name))
|
|
435
|
+
]
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
class Difference_density_image_maker(Orbital_image_maker):
|
|
439
|
+
|
|
440
|
+
# Name of the section where we get some specific configs.
|
|
441
|
+
options_name = "difference_density"
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
class Spin_density_image_maker(Orbital_image_maker):
|
|
445
|
+
"""
|
|
446
|
+
Class for creating spin density images.
|
|
447
|
+
"""
|
|
448
|
+
|
|
449
|
+
vmd_script ="generate_spin_images.tcl"
|
|
450
|
+
|
|
451
|
+
# Name of the section where we get some specific configs.
|
|
452
|
+
options_name = "spin"
|
|
453
|
+
|
|
454
|
+
def __init__(self, *args, spin = "both", **kwargs):
|
|
455
|
+
"""
|
|
456
|
+
Constructor for Spin_density_image_maker objects.
|
|
457
|
+
|
|
458
|
+
See Orbital_image_maker for a complete constructor.
|
|
459
|
+
:param spin: A string indicating which net-spins to render, either 'positive', 'negative' or 'both'.
|
|
460
|
+
"""
|
|
461
|
+
super().__init__(*args, **kwargs)
|
|
462
|
+
self.spin = spin
|
|
463
|
+
|
|
464
|
+
@property
|
|
465
|
+
def VMD_signature(self):
|
|
466
|
+
"""
|
|
467
|
+
The arguments which we'll pass to VMD, inheriting classes can implement this method if they have a different VMD call signature.
|
|
468
|
+
"""
|
|
469
|
+
return [
|
|
470
|
+
"{}".format(self.vmd_executable),
|
|
471
|
+
"-dispdev", "none",
|
|
472
|
+
"-e", "{}".format(self.vmd_script_path.name),
|
|
473
|
+
"-args",
|
|
474
|
+
"{}".format(self.safe_name(Path(str(self.input_file)).name)),
|
|
475
|
+
"{}".format(self.tcl_common_path.name),
|
|
476
|
+
"{}".format(self.rendering_style),
|
|
477
|
+
"{}".format(fabs(self.isovalue)),
|
|
478
|
+
"{}".format(self.spin),
|
|
479
|
+
"{}".format(self.prepared_translations),
|
|
480
|
+
"{}".format(self.prepared_rotations),
|
|
481
|
+
"{}".format(self.safe_name(self.file_path['x0y0z0'].with_suffix(self.scene_file_extension).name)),
|
|
482
|
+
"{}".format(self.safe_name(self.file_path['x90y0z0'].with_suffix(self.scene_file_extension).name)),
|
|
483
|
+
"{}".format(self.safe_name(self.file_path['x0y90z0'].with_suffix(self.scene_file_extension).name)),
|
|
484
|
+
"{}".format(self.safe_name(self.file_path['x45y45z45'].with_suffix(self.scene_file_extension).name))
|
|
485
|
+
]
|
|
486
|
+
|
|
487
|
+
class Alpha_orbital_image_maker(Orbital_image_maker):
|
|
488
|
+
pass
|
|
489
|
+
#cubegen_type = "AMO"
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
class Beta_orbital_image_maker(Orbital_image_maker):
|
|
493
|
+
pass
|
|
494
|
+
#cubegen_type = "BMO"
|
|
495
|
+
|
|
496
|
+
class Density_image_maker(Orbital_image_maker):
|
|
497
|
+
"""
|
|
498
|
+
Class for creating spin density images.
|
|
499
|
+
"""
|
|
500
|
+
|
|
501
|
+
vmd_script ="generate_density_images.tcl"
|
|
502
|
+
|
|
503
|
+
# Name of the section where we get some specific configs.
|
|
504
|
+
options_name = "density"
|
|
505
|
+
|
|
506
|
+
def __init__(self, *args, **kwargs):
|
|
507
|
+
"""
|
|
508
|
+
Constructor for Spin_density_image_maker objects.
|
|
509
|
+
|
|
510
|
+
See Orbital_image_maker for a complete constructor.
|
|
511
|
+
"""
|
|
512
|
+
super().__init__(*args, **kwargs)
|
|
513
|
+
|
|
514
|
+
@property
|
|
515
|
+
def type(self):
|
|
516
|
+
"""
|
|
517
|
+
The density type.
|
|
518
|
+
"""
|
|
519
|
+
return self.input_file.type
|
|
520
|
+
|
|
521
|
+
@property
|
|
522
|
+
def VMD_signature(self):
|
|
523
|
+
"""
|
|
524
|
+
The arguments which we'll pass to VMD, inheriting classes can implement this method if they have a different VMD call signature.
|
|
525
|
+
"""
|
|
526
|
+
return [
|
|
527
|
+
"{}".format(self.vmd_executable),
|
|
528
|
+
"-dispdev", "none",
|
|
529
|
+
"-e", "{}".format(self.vmd_script_path.name),
|
|
530
|
+
"-args",
|
|
531
|
+
"{}".format(self.safe_name(Path(str(self.input_file)).name)),
|
|
532
|
+
"{}".format(self.tcl_common_path.name),
|
|
533
|
+
"{}".format(self.rendering_style),
|
|
534
|
+
"{}".format(fabs(self.isovalue)),
|
|
535
|
+
"{}".format(self.prepared_translations),
|
|
536
|
+
"{}".format(self.prepared_rotations),
|
|
537
|
+
"{}".format(self.safe_name(self.file_path['x0y0z0'].with_suffix(self.scene_file_extension).name)),
|
|
538
|
+
"{}".format(self.safe_name(self.file_path['x90y0z0'].with_suffix(self.scene_file_extension).name)),
|
|
539
|
+
"{}".format(self.safe_name(self.file_path['x0y90z0'].with_suffix(self.scene_file_extension).name)),
|
|
540
|
+
"{}".format(self.safe_name(self.file_path['x45y45z45'].with_suffix(self.scene_file_extension).name))
|
|
541
|
+
]
|
|
542
|
+
|
|
543
|
+
class Combined_orbital_image_maker(VMD_image_maker):
|
|
544
|
+
"""
|
|
545
|
+
Class for creating images with both the HOMO and LUMO shown together.
|
|
546
|
+
"""
|
|
547
|
+
|
|
548
|
+
vmd_script ="generate_combined_orbital_images.tcl"
|
|
549
|
+
|
|
550
|
+
def __init__(self, *args, HOMO_cube_file = None, LUMO_cube_file = None, **kwargs):
|
|
551
|
+
"""
|
|
552
|
+
Constructor for combined orbital image maker objects.
|
|
553
|
+
|
|
554
|
+
:param output: Path to write to. See the constructor for VMD_image_maker for how this works.
|
|
555
|
+
:param HOMO_cube_file: Path to the HOMO cube file.
|
|
556
|
+
:param LUMO_cube_file: Path to the LUMO cube file.
|
|
557
|
+
:param *args: See the constructor for VMD_image_maker for further options.
|
|
558
|
+
:param **kwargs: See the constructor for VMD_image_maker for further options.
|
|
559
|
+
"""
|
|
560
|
+
super().__init__(*args, cube_file = None, **kwargs)
|
|
561
|
+
self.HOMO_cube_file = HOMO_cube_file
|
|
562
|
+
self.LUMO_cube_file = LUMO_cube_file
|
|
563
|
+
|
|
564
|
+
@classmethod
|
|
565
|
+
def from_options(self, output, *, HOMO_cube_file = None, LUMO_cube_file = None, rotations = None, options, **kwargs):
|
|
566
|
+
"""
|
|
567
|
+
Constructor that takes a dictionary of config like options.
|
|
568
|
+
"""
|
|
569
|
+
return self(
|
|
570
|
+
output,
|
|
571
|
+
HOMO_cube_file = HOMO_cube_file,
|
|
572
|
+
LUMO_cube_file = LUMO_cube_file,
|
|
573
|
+
rotations = rotations,
|
|
574
|
+
auto_crop = options['render']['auto_crop'],
|
|
575
|
+
rendering_style = options['render']['vmd']['rendering_style'],
|
|
576
|
+
resolution = options['render']['resolution'],
|
|
577
|
+
isovalue = options['render'][self.options_name]['isovalue'],
|
|
578
|
+
use_existing = options['render']['use_existing'],
|
|
579
|
+
dont_modify = not options['render']['enable_rendering'],
|
|
580
|
+
vmd_executable = options['render']['vmd']['executable'],
|
|
581
|
+
tachyon_executable = options['render']['vmd']['tachyon'],
|
|
582
|
+
vmd_logging = options['logging']['render_logging'],
|
|
583
|
+
**kwargs
|
|
584
|
+
)
|
|
585
|
+
|
|
586
|
+
def check_can_make(self):
|
|
587
|
+
"""
|
|
588
|
+
Check whether it is feasible to try and render the image(s) that we represent.
|
|
589
|
+
|
|
590
|
+
Reasons for rendering not being possible are varied and are up to the inheriting class, but include eg, a required input (cube, fchk) file not being given.
|
|
591
|
+
|
|
592
|
+
This method returns nothing, but will raise a File_maker_exception exception if the rendering is not possible.
|
|
593
|
+
"""
|
|
594
|
+
try:
|
|
595
|
+
if self.HOMO_cube_file is None or self.HOMO_cube_file.safe_get_file() is None:
|
|
596
|
+
raise File_maker_exception(self, "No HOMO cube file is available")
|
|
597
|
+
except AttributeError:
|
|
598
|
+
if not hasattr(self.HOMO_cube_file, 'safe_get_file'):
|
|
599
|
+
# Input file does not have a safe_get_file() method.
|
|
600
|
+
pass
|
|
601
|
+
|
|
602
|
+
else:
|
|
603
|
+
raise
|
|
604
|
+
|
|
605
|
+
try:
|
|
606
|
+
if self.LUMO_cube_file is None or self.LUMO_cube_file.safe_get_file() is None:
|
|
607
|
+
raise File_maker_exception(self, "No LUMO cube file is available")
|
|
608
|
+
|
|
609
|
+
except AttributeError:
|
|
610
|
+
if not hasattr(self.LUMO_cube_file, 'safe_get_file'):
|
|
611
|
+
# Input file does not have a safe_get_file() method.
|
|
612
|
+
pass
|
|
613
|
+
|
|
614
|
+
else:
|
|
615
|
+
raise
|
|
616
|
+
|
|
617
|
+
@property
|
|
618
|
+
def inputs(self):
|
|
619
|
+
"""
|
|
620
|
+
A dictionary of all the input files required by VMD for this image.
|
|
621
|
+
|
|
622
|
+
inputs is a dictionary where each key is the path to the locally accessible file,
|
|
623
|
+
and each value is the true location.
|
|
624
|
+
"""
|
|
625
|
+
working_directory = self.output.parent
|
|
626
|
+
return {
|
|
627
|
+
Path(working_directory, self.vmd_script_path.name): self.vmd_script_path,
|
|
628
|
+
Path(working_directory, self.tcl_common_path.name): self.tcl_common_path,
|
|
629
|
+
Path(working_directory, self.safe_name(Path(str(self.HOMO_cube_file)).name)): Path(str(self.HOMO_cube_file)).absolute(),
|
|
630
|
+
Path(working_directory, self.safe_name(Path(str(self.LUMO_cube_file)).name)): Path(str(self.LUMO_cube_file)).absolute()
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
@property
|
|
634
|
+
def VMD_signature(self):
|
|
635
|
+
"""
|
|
636
|
+
The arguments which we'll pass to VMD, inheriting classes can implement this method if they have a different VMD call signature.
|
|
637
|
+
"""
|
|
638
|
+
return [
|
|
639
|
+
"{}".format(self.vmd_executable),
|
|
640
|
+
"-dispdev", "none",
|
|
641
|
+
"-e", "{}".format(self.vmd_script_path.name),
|
|
642
|
+
"-args",
|
|
643
|
+
"{}".format(self.safe_name(Path(str(self.HOMO_cube_file)).name)),
|
|
644
|
+
"{}".format(self.safe_name(Path(str(self.LUMO_cube_file)).name)),
|
|
645
|
+
"{}".format(self.tcl_common_path.name),
|
|
646
|
+
"{}".format(self.rendering_style),
|
|
647
|
+
"{}".format(fabs(self.isovalue)),
|
|
648
|
+
"{}".format(self.prepared_translations),
|
|
649
|
+
"{}".format(self.prepared_rotations),
|
|
650
|
+
"{}".format(self.safe_name(self.file_path['x0y0z0'].with_suffix(self.scene_file_extension).name)),
|
|
651
|
+
"{}".format(self.safe_name(self.file_path['x90y0z0'].with_suffix(self.scene_file_extension).name)),
|
|
652
|
+
"{}".format(self.safe_name(self.file_path['x0y90z0'].with_suffix(self.scene_file_extension).name)),
|
|
653
|
+
"{}".format(self.safe_name(self.file_path['x45y45z45'].with_suffix(self.scene_file_extension).name))
|
|
654
|
+
]
|
|
655
|
+
|
|
656
|
+
|
|
657
|
+
|
|
658
|
+
class Dipole_image_maker(Structure_image_maker):
|
|
659
|
+
"""
|
|
660
|
+
Class for creating dipole images.
|
|
661
|
+
"""
|
|
662
|
+
|
|
663
|
+
vmd_script ="generate_dipole_images.tcl"
|
|
664
|
+
|
|
665
|
+
|
|
666
|
+
def __init__(self, *args, dipole_moment = None, magnetic_dipole_moment = None, scaling = 1, magnetic_scaling = 1, **kwargs):
|
|
667
|
+
"""
|
|
668
|
+
Constructor for Dipole_image_maker objects.
|
|
669
|
+
|
|
670
|
+
:param output: Path to write to. See the constructor for VMD_image_maker for how this works.
|
|
671
|
+
:param cube_file: A Gaussian cube file to use to render the new images.
|
|
672
|
+
:param dipole_moment: A Dipole_moment object that will be rendered as a red arrow in the scene.
|
|
673
|
+
:param magnetic_dipole_moment: A second dipole moment object that will be rendered as a blue arrow in the scene.
|
|
674
|
+
:param scaling: A factor to scale the dipole moment by.
|
|
675
|
+
:param magnetic_scaling: A factor to scale the magnetic dipole moment by.
|
|
676
|
+
:param *args: See the constructor for VMD_image_maker for further options.
|
|
677
|
+
:param **kwargs: See the constructor for VMD_image_maker for further options.
|
|
678
|
+
"""
|
|
679
|
+
super().__init__(*args, **kwargs)
|
|
680
|
+
self.dipole_moment = dipole_moment
|
|
681
|
+
self.magnetic_dipole_moment = magnetic_dipole_moment
|
|
682
|
+
self.scaling = scaling
|
|
683
|
+
self.magnetic_scaling = magnetic_scaling
|
|
684
|
+
self.electric_arrow_colour = "red"
|
|
685
|
+
self.magnetic_arrow_colour = "green"
|
|
686
|
+
|
|
687
|
+
def get_coords(self, dipole, scaling):
|
|
688
|
+
if dipole is None:
|
|
689
|
+
return ( (0.0,0.0,0.0), (0.0,0.0,0.0))
|
|
690
|
+
|
|
691
|
+
else:
|
|
692
|
+
return (
|
|
693
|
+
tuple([coord * scaling for coord in dipole._origin_coords]),
|
|
694
|
+
tuple([coord * scaling for coord in dipole._vector_coords])
|
|
695
|
+
)
|
|
696
|
+
|
|
697
|
+
@property
|
|
698
|
+
def VMD_signature(self):
|
|
699
|
+
"""
|
|
700
|
+
The arguments which we'll pass to VMD, inheriting classes can implement this method if they have a different VMD call signature.
|
|
701
|
+
"""
|
|
702
|
+
return [
|
|
703
|
+
"{}".format(self.vmd_executable),
|
|
704
|
+
"-dispdev", "none",
|
|
705
|
+
"-e", "{}".format(self.vmd_script_path.name),
|
|
706
|
+
"-args",
|
|
707
|
+
"{}".format(self.safe_name(Path(str(self.input_file)).name)),
|
|
708
|
+
"{}".format(self.tcl_common_path.name),
|
|
709
|
+
"{}".format(self.rendering_style),
|
|
710
|
+
"{}".format(self.prepared_translations),
|
|
711
|
+
"{}".format(self.prepared_rotations),
|
|
712
|
+
# We don't use the normal origin_coords/vector_coords because these are already rotated, while we want/need to do this rotation with the camera in VMD.
|
|
713
|
+
# Hence use _origin_coords/_vector_coods, which aren't rotated.
|
|
714
|
+
# Dipole 1 (electric).
|
|
715
|
+
"{}:{}:{}".format(*self.get_coords(self.dipole_moment, self.scaling)[0]),
|
|
716
|
+
"{}:{}:{}".format(*self.get_coords(self.dipole_moment, self.scaling)[1]),
|
|
717
|
+
# Dipole 2 (magnetic).
|
|
718
|
+
"{}:{}:{}".format(*self.get_coords(self.magnetic_dipole_moment, self.magnetic_scaling)[0]),
|
|
719
|
+
"{}:{}:{}".format(*self.get_coords(self.magnetic_dipole_moment, self.magnetic_scaling)[1]),
|
|
720
|
+
"{}".format(self.safe_name(self.file_path['x0y0z0'].with_suffix(self.scene_file_extension).name)),
|
|
721
|
+
"{}".format(self.safe_name(self.file_path['x90y0z0'].with_suffix(self.scene_file_extension).name)),
|
|
722
|
+
"{}".format(self.safe_name(self.file_path['x0y90z0'].with_suffix(self.scene_file_extension).name)),
|
|
723
|
+
"{}".format(self.safe_name(self.file_path['x45y45z45'].with_suffix(self.scene_file_extension).name))
|
|
724
|
+
]
|
|
725
|
+
|
|
726
|
+
|
|
727
|
+
class Permanent_dipole_image_maker(Dipole_image_maker):
|
|
728
|
+
"""
|
|
729
|
+
Class for creating dipole images.
|
|
730
|
+
"""
|
|
731
|
+
|
|
732
|
+
def __init__(self, *args, dipole_moment = None, scaling = 1, **kwargs):
|
|
733
|
+
"""
|
|
734
|
+
Constructor for Dipole_image_maker objects.
|
|
735
|
+
|
|
736
|
+
:param output: Path to write to. See the constructor for VMD_image_maker for how this works.
|
|
737
|
+
:param cube_file: A Gaussian cube file to use to render the new images.
|
|
738
|
+
:param dipole_moment: A Dipole_moment object that will be rendered as a red arrow in the scene.
|
|
739
|
+
:param scaling: A factor to scale the dipole moment by.
|
|
740
|
+
:param *args: See the constructor for VMD_image_maker for further options.
|
|
741
|
+
:param **kwargs: See the constructor for VMD_image_maker for further options.
|
|
742
|
+
"""
|
|
743
|
+
super().__init__(*args, dipole_moment = dipole_moment, scaling = scaling, **kwargs)
|
|
744
|
+
|
|
745
|
+
@classmethod
|
|
746
|
+
def from_options(self, output, *, dipole_moment = None, cube_file = None, rotations = None, options, **kwargs):
|
|
747
|
+
"""
|
|
748
|
+
Constructor that takes a dictionary of config like options.
|
|
749
|
+
"""
|
|
750
|
+
return self(
|
|
751
|
+
output,
|
|
752
|
+
cube_file = cube_file,
|
|
753
|
+
rotations = rotations,
|
|
754
|
+
dipole_moment = dipole_moment,
|
|
755
|
+
auto_crop = options['render']['auto_crop'],
|
|
756
|
+
rendering_style = options['render']['vmd']['rendering_style'],
|
|
757
|
+
resolution = options['render']['resolution'],
|
|
758
|
+
isovalue = options['render'][self.options_name]['isovalue'],
|
|
759
|
+
use_existing = options['render']['use_existing'],
|
|
760
|
+
dont_modify = not options['render']['enable_rendering'],
|
|
761
|
+
vmd_executable = options['render']['vmd']['executable'],
|
|
762
|
+
tachyon_executable = options['render']['vmd']['tachyon'],
|
|
763
|
+
vmd_logging = options['logging']['render_logging'],
|
|
764
|
+
scaling = options['render']['dipole_moment']['scaling'],
|
|
765
|
+
**kwargs
|
|
766
|
+
)
|
|
767
|
+
|
|
768
|
+
def check_can_make(self):
|
|
769
|
+
"""
|
|
770
|
+
Check whether it is feasible to try and render the image(s) that we represent.
|
|
771
|
+
|
|
772
|
+
Reasons for rendering not being possible are varied and are up to the inheriting class, but include eg, a required input (cube, fchk) file not being given.
|
|
773
|
+
|
|
774
|
+
This method returns nothing, but will raise a File_maker_exception exception if the rendering is not possible.
|
|
775
|
+
"""
|
|
776
|
+
super().check_can_make()
|
|
777
|
+
|
|
778
|
+
# Also make sure we have a dipole.
|
|
779
|
+
if self.dipole_moment is None:
|
|
780
|
+
raise File_maker_exception(self, "No dipole moment is available.")
|
|
781
|
+
|
|
782
|
+
|
|
783
|
+
class Transition_dipole_image_maker(Dipole_image_maker):
|
|
784
|
+
"""
|
|
785
|
+
Class for creating TDM images.
|
|
786
|
+
"""
|
|
787
|
+
|
|
788
|
+
def check_can_make(self):
|
|
789
|
+
"""
|
|
790
|
+
Check whether it is feasible to try and render the image(s) that we represent.
|
|
791
|
+
|
|
792
|
+
Reasons for rendering not being possible are varied and are up to the inheriting class, but include eg, a required input (cube, fchk) file not being given.
|
|
793
|
+
|
|
794
|
+
This method returns nothing, but will raise a File_maker_exception exception if the rendering is not possible.
|
|
795
|
+
"""
|
|
796
|
+
super().check_can_make()
|
|
797
|
+
|
|
798
|
+
# Also make sure we have a dipole.
|
|
799
|
+
if self.dipole_moment is None and self.magnetic_dipole_moment is None:
|
|
800
|
+
raise File_maker_exception(self, "No dipole moment is available.")
|
|
801
|
+
|
|
802
|
+
@classmethod
|
|
803
|
+
def from_options(self, output, *, dipole_moment = None, magnetic_dipole_moment, cube_file = None, rotations = None, options, **kwargs):
|
|
804
|
+
"""
|
|
805
|
+
Constructor that takes a dictionary of config like options.
|
|
806
|
+
"""
|
|
807
|
+
return self(
|
|
808
|
+
output,
|
|
809
|
+
cube_file = cube_file,
|
|
810
|
+
rotations = rotations,
|
|
811
|
+
dipole_moment = dipole_moment,
|
|
812
|
+
magnetic_dipole_moment = magnetic_dipole_moment,
|
|
813
|
+
auto_crop = options['render']['auto_crop'],
|
|
814
|
+
rendering_style = options['render']['vmd']['rendering_style'],
|
|
815
|
+
resolution = options['render']['resolution'],
|
|
816
|
+
isovalue = options['render'][self.options_name]['isovalue'],
|
|
817
|
+
use_existing = options['render']['use_existing'],
|
|
818
|
+
dont_modify = not options['render']['enable_rendering'],
|
|
819
|
+
vmd_executable = options['render']['vmd']['executable'],
|
|
820
|
+
tachyon_executable = options['render']['vmd']['tachyon'],
|
|
821
|
+
vmd_logging = options['logging']['render_logging'],
|
|
822
|
+
scaling = options['render']['transition_dipole_moment']['electric_scaling'],
|
|
823
|
+
magnetic_scaling = options['render']['transition_dipole_moment']['magnetic_scaling'],
|
|
824
|
+
**kwargs
|
|
825
|
+
)
|
|
826
|
+
|