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.
Files changed (111) hide show
  1. digichem/__init__.py +75 -0
  2. digichem/basis.py +116 -0
  3. digichem/config/README +3 -0
  4. digichem/config/__init__.py +5 -0
  5. digichem/config/base.py +321 -0
  6. digichem/config/locations.py +14 -0
  7. digichem/config/parse.py +90 -0
  8. digichem/config/util.py +117 -0
  9. digichem/data/README +4 -0
  10. digichem/data/batoms/COPYING +18 -0
  11. digichem/data/batoms/LICENSE +674 -0
  12. digichem/data/batoms/README +2 -0
  13. digichem/data/batoms/__init__.py +0 -0
  14. digichem/data/batoms/batoms-renderer.py +351 -0
  15. digichem/data/config/digichem.yaml +714 -0
  16. digichem/data/functionals.csv +15 -0
  17. digichem/data/solvents.csv +185 -0
  18. digichem/data/tachyon/COPYING.md +5 -0
  19. digichem/data/tachyon/LICENSE +30 -0
  20. digichem/data/tachyon/tachyon_LINUXAMD64 +0 -0
  21. digichem/data/vmd/common.tcl +468 -0
  22. digichem/data/vmd/generate_combined_orbital_images.tcl +70 -0
  23. digichem/data/vmd/generate_density_images.tcl +45 -0
  24. digichem/data/vmd/generate_dipole_images.tcl +68 -0
  25. digichem/data/vmd/generate_orbital_images.tcl +57 -0
  26. digichem/data/vmd/generate_spin_images.tcl +66 -0
  27. digichem/data/vmd/generate_structure_images.tcl +40 -0
  28. digichem/datas.py +14 -0
  29. digichem/exception/__init__.py +7 -0
  30. digichem/exception/base.py +133 -0
  31. digichem/exception/uncatchable.py +63 -0
  32. digichem/file/__init__.py +1 -0
  33. digichem/file/base.py +364 -0
  34. digichem/file/cube.py +284 -0
  35. digichem/file/fchk.py +94 -0
  36. digichem/file/prattle.py +277 -0
  37. digichem/file/types.py +97 -0
  38. digichem/image/__init__.py +6 -0
  39. digichem/image/base.py +113 -0
  40. digichem/image/excited_states.py +335 -0
  41. digichem/image/graph.py +293 -0
  42. digichem/image/orbitals.py +239 -0
  43. digichem/image/render.py +617 -0
  44. digichem/image/spectroscopy.py +797 -0
  45. digichem/image/structure.py +115 -0
  46. digichem/image/vmd.py +826 -0
  47. digichem/input/__init__.py +3 -0
  48. digichem/input/base.py +78 -0
  49. digichem/input/digichem_input.py +500 -0
  50. digichem/input/gaussian.py +140 -0
  51. digichem/log.py +179 -0
  52. digichem/memory.py +166 -0
  53. digichem/misc/__init__.py +4 -0
  54. digichem/misc/argparse.py +44 -0
  55. digichem/misc/base.py +61 -0
  56. digichem/misc/io.py +239 -0
  57. digichem/misc/layered_dict.py +285 -0
  58. digichem/misc/text.py +139 -0
  59. digichem/misc/time.py +73 -0
  60. digichem/parse/__init__.py +13 -0
  61. digichem/parse/base.py +220 -0
  62. digichem/parse/cclib.py +138 -0
  63. digichem/parse/dump.py +253 -0
  64. digichem/parse/gaussian.py +130 -0
  65. digichem/parse/orca.py +96 -0
  66. digichem/parse/turbomole.py +201 -0
  67. digichem/parse/util.py +523 -0
  68. digichem/result/__init__.py +6 -0
  69. digichem/result/alignment/AA.py +114 -0
  70. digichem/result/alignment/AAA.py +61 -0
  71. digichem/result/alignment/FAP.py +148 -0
  72. digichem/result/alignment/__init__.py +3 -0
  73. digichem/result/alignment/base.py +310 -0
  74. digichem/result/angle.py +153 -0
  75. digichem/result/atom.py +742 -0
  76. digichem/result/base.py +258 -0
  77. digichem/result/dipole_moment.py +332 -0
  78. digichem/result/emission.py +402 -0
  79. digichem/result/energy.py +323 -0
  80. digichem/result/excited_state.py +821 -0
  81. digichem/result/ground_state.py +94 -0
  82. digichem/result/metadata.py +644 -0
  83. digichem/result/multi.py +98 -0
  84. digichem/result/nmr.py +1086 -0
  85. digichem/result/orbital.py +647 -0
  86. digichem/result/result.py +244 -0
  87. digichem/result/soc.py +272 -0
  88. digichem/result/spectroscopy.py +514 -0
  89. digichem/result/tdm.py +267 -0
  90. digichem/result/vibration.py +167 -0
  91. digichem/test/__init__.py +6 -0
  92. digichem/test/conftest.py +4 -0
  93. digichem/test/test_basis.py +71 -0
  94. digichem/test/test_calculate.py +30 -0
  95. digichem/test/test_config.py +78 -0
  96. digichem/test/test_cube.py +369 -0
  97. digichem/test/test_exception.py +16 -0
  98. digichem/test/test_file.py +104 -0
  99. digichem/test/test_image.py +337 -0
  100. digichem/test/test_input.py +64 -0
  101. digichem/test/test_parsing.py +79 -0
  102. digichem/test/test_prattle.py +36 -0
  103. digichem/test/test_result.py +489 -0
  104. digichem/test/test_translate.py +112 -0
  105. digichem/test/util.py +207 -0
  106. digichem/translate.py +591 -0
  107. digichem_core-6.0.0rc1.dist-info/METADATA +96 -0
  108. digichem_core-6.0.0rc1.dist-info/RECORD +111 -0
  109. digichem_core-6.0.0rc1.dist-info/WHEEL +4 -0
  110. digichem_core-6.0.0rc1.dist-info/licenses/COPYING.md +10 -0
  111. digichem_core-6.0.0rc1.dist-info/licenses/LICENSE +11 -0
@@ -0,0 +1,351 @@
1
+ # Render something with Beautiful Atoms.
2
+ # To use this script, run something like:
3
+ #
4
+ # # To disabled possibly conflicting packages outside of the conda env.
5
+ # PYTHONNOUSERSITE=1
6
+ # blender -b -P batoms-renderer.py
7
+ #
8
+ # Where 'blender' is the path to the Beautiful Atoms Blender executable.
9
+
10
+ import sys
11
+ import argparse
12
+ import itertools
13
+ import bpy
14
+ import yaml
15
+ import math
16
+ from pathlib import Path
17
+ import logging
18
+
19
+ import ase.io
20
+ from batoms import Batoms
21
+ from batoms.utils.butils import object_mode
22
+
23
+ def add_molecule(
24
+ cube_file,
25
+ name,
26
+ rotations = None,
27
+ visible = True,
28
+ isovalues = None,
29
+ isotype = "both",
30
+ primary_color = [1, 0.058, 0.0, 0.55],
31
+ secondary_color = [0.1, 0.1, 0.9, 0.55],
32
+ style = "default"
33
+ ):
34
+ """
35
+ """
36
+ rotations = [] if rotations is None else rotations
37
+ isovalues = [] if isovalues is None else isovalues
38
+
39
+ surface_settings = []
40
+ for isovalue in isovalues:
41
+ if isotype in ["both", "positive"]:
42
+ surface_settings.append({'level': isovalue, 'color': primary_color, 'material_style': style})
43
+
44
+ if isotype in ["both", "negative"]:
45
+ surface_settings.append({'level': -isovalue, 'color': secondary_color, 'material_style': style})
46
+
47
+
48
+ # Load the input data.
49
+ cube = ase.io.read(cube_file, format="cube", read_data=True, full_output=True)
50
+
51
+ # The centre of the cube is often offset, fix that by shifting the atoms.
52
+ cube["atoms"].translate(-cube["origin"][0:3])
53
+
54
+ # Get the mol object.
55
+ mol = Batoms(name, from_ase = cube["atoms"])
56
+
57
+ # Set some look and feel options.
58
+ # Change molecule style.
59
+ mol.model_style = 1
60
+
61
+ # Hide cell boundaries.
62
+ mol.cell.hide = True
63
+
64
+ # Colour tuning.
65
+ # Carbon to 'black'.
66
+ try:
67
+ mol["C"].color = (0.095, 0.095, 0.095, 1)
68
+ except AttributeError:
69
+ pass
70
+ try:
71
+ mol["B"].color = (1.0, 0.396, 0.468, 1)
72
+ except AttributeError:
73
+ pass
74
+
75
+ if not visible:
76
+ mol.hide = True
77
+
78
+ # Add volumes.
79
+ if len(surface_settings) != 0:
80
+ mol.volumetric_data['surface'] = cube['data']
81
+
82
+ for index, settings in enumerate(surface_settings):
83
+ mol.isosurface.settings[index+1] = settings
84
+
85
+ mol.isosurface.draw()
86
+
87
+ # Now move the entire molecule (isosurface and all) back to the origin.
88
+ mol.translate(cube["origin"][0:3])
89
+
90
+ # Fix the origin point so we can still rotate properly.
91
+ # For some reason, this code moves the bond objects to a new location?
92
+ # object_mode()
93
+ # bpy.ops.object.select_all(action='DESELECT')
94
+ # mol.obj.select_set(True)
95
+ # bpy.context.view_layer.objects.active = mol.obj
96
+ # bpy.ops.object.origin_set(type='ORIGIN_CURSOR')
97
+
98
+ # If we have any rotations, apply those.
99
+ for axis, angle in rotations:
100
+ # Convert to degree.
101
+ # Not used.
102
+ degree = angle * (180/math.pi)
103
+
104
+ if axis == 0:
105
+ axis = "x"
106
+
107
+ elif axis == 1:
108
+ axis = "y"
109
+
110
+ elif axis == 2:
111
+ axis = "z"
112
+
113
+ else:
114
+ raise ValueError("Unknown rotation axis '{}'".format(axis))
115
+
116
+ #mol.rotate(degree, axis)
117
+
118
+ # Taken from batoms source (collection.py: 51)
119
+ object_mode()
120
+ bpy.ops.object.select_all(action='DESELECT')
121
+ mol.obj.select_set(True)
122
+ bpy.context.view_layer.objects.active = mol.obj
123
+ bpy.ops.transform.rotate(value=angle, orient_axis=axis.upper(),
124
+ center_override = (0,0,0))
125
+
126
+ return mol
127
+
128
+ # Adapted from https://blender.stackexchange.com/questions/5898/how-can-i-create-a-cylinder-linking-two-points-with-python?newreg=f372ba9448694f5b97879a6dab963cee
129
+ def draw_primitive(start, end, radius, mesh_type, color, collection = None):
130
+ """
131
+ """
132
+ dx = end[0] - start[0]
133
+ dy = end[1] - start[1]
134
+ dz = end[2] - start[2]
135
+ dist = math.sqrt(dx**2 + dy**2 + dz**2)
136
+
137
+ # First draw the shape.
138
+ if mesh_type == "cylinder":
139
+ bpy.ops.mesh.primitive_cylinder_add(
140
+ radius = radius,
141
+ depth = dist,
142
+ vertices = 32,
143
+ location = (dx/2 + start[0], dy/2 + start[1], dz/2 + start[2])
144
+ )
145
+ elif mesh_type == "cone":
146
+ bpy.ops.mesh.primitive_cone_add(
147
+ radius1 = radius,
148
+ depth = dist,
149
+ vertices = 32,
150
+ location = (dx/2 + start[0], dy/2 + start[1], dz/2 + start[2])
151
+ )
152
+ else:
153
+ raise ValueError("Unknown mesh type '{}'".format(mesh_type))
154
+
155
+ # Get a reference to the object we just made.
156
+ obj = bpy.context.active_object
157
+
158
+ phi = math.atan2(dy, dx)
159
+ theta = math.acos(dz/dist)
160
+
161
+ bpy.context.object.rotation_euler[1] = theta
162
+ bpy.context.object.rotation_euler[2] = phi
163
+
164
+ # Get material
165
+ mat = bpy.data.materials.new(name="Material")
166
+
167
+ # assign to 1st material slot
168
+ obj.active_material = mat
169
+
170
+ mat.use_nodes = True
171
+ tree = mat.node_tree
172
+ nodes = tree.nodes
173
+ bsdf = nodes["Principled BSDF"]
174
+ bsdf.inputs["Base Color"].default_value = color
175
+ bsdf.inputs["Metallic"].default_value = 0.1
176
+ bsdf.inputs["Specular"].default_value = 0.2
177
+ bsdf.inputs["Roughness"].default_value = 0.2
178
+ mat.diffuse_color = color
179
+
180
+ # If we've been asked to, asign our new object to a given collection.
181
+ # First unlink from any old collections
182
+ for coll in obj.users_collection:
183
+ # Unlink the object
184
+ coll.objects.unlink(obj)
185
+
186
+ # Link each object to the target collection
187
+ collection.objects.link(obj)
188
+
189
+
190
+ def draw_arrow(start, end, radius, color, split = 0.9, collection = None):
191
+ # Decide what proportion of the total vector to dedicate to the arrow stem and head.
192
+ dx = end[0] - start[0]
193
+ dy = end[1] - start[1]
194
+ dz = end[2] - start[2]
195
+ dist = math.sqrt(dx**2 + dy**2 + dz**2)
196
+
197
+ join = (dx * split + start[0], dy * split + start[1], dz * split + start[2])
198
+ draw_primitive(start, join, radius, "cylinder", color, collection = collection)
199
+ draw_primitive(join, end, radius*2, "cone", color, collection = collection)
200
+
201
+
202
+ def main():
203
+ parser = argparse.ArgumentParser(
204
+ prog='Beautiful Atoms Renderer',
205
+ description='Render images with BAtoms')
206
+
207
+ parser.add_argument("cube_file", help = "Path to the cube file to read")
208
+ parser.add_argument("output", help = "File to write to")
209
+ parser.add_argument("--second_cube", help = "Optional second cube file to read additional isosurface data from", default = None)
210
+ parser.add_argument("--isovalues", help = "List of isovalues to render", nargs = "*", type = float, default = [])
211
+ parser.add_argument("--isotype", help = "Whether to render positive, negative or both isosurfaces for each isovalue", choices = ["positive", "negative", "both"], default = "both")
212
+ parser.add_argument("--isocolor", help = "The colouring method to use for isosurfaces", choices = ["sign", "cube"], default = "sign")
213
+ parser.add_argument("--primary-color", help = "RGBA for one of the colors to use for isosurfaces", type = float, nargs = 4, default = [0.1, 0.1, 0.9, 0.7])
214
+ parser.add_argument("--secondary-color", help = "RGBA for the other color to use for isosurfaces", type = float, nargs = 4, default = [1, 0.058, 0.0, 0.7])
215
+ parser.add_argument("--style", help = "Material style for isosurfaces", choices = ('default', 'metallic', 'plastic', 'ceramic', 'mirror'), default = "default")
216
+ parser.add_argument("--cpus", help = "Number of parallel CPUs to use for rendering", type = int, default = 1)
217
+ parser.add_argument("--use-gpu", help = "Whether to enable GPU rendering", action = "store_true")
218
+ parser.add_argument("--orientation", help = "The orientation to render from, as x, y, z values", nargs = 3, type = float, default = [0, 0, 1])
219
+ parser.add_argument("--resolution", help = "The output resolution in px", type = int, default = 1024)
220
+ parser.add_argument("--render-samples", help = "The maximum number of render samples, more generally results in higher quality but longer render times", type = int, default = 256)
221
+ parser.add_argument("--rotations", help = "A list of rotations (in JSON) to rotate the molecule to a given alignment. The first item in each list item is the axis to rotate about (0=x, 1=y, 2=z), the second is the angle to rotate by (in radians)", nargs = "*", default = [])
222
+ parser.add_argument("--dipoles", help = "Draw dipoles from a list of the following data (in JSON): 0) start coord, 1) end coord, 2) RGBA color information", nargs = "*", default = [])
223
+ parser.add_argument("--alpha", help = "Override the opacity value for all molecule objects (but not dipoles) to this value, useful for showing dipole arrows more clearly", default = None, type = float)
224
+ parser.add_argument("--perspective", help = "The perspective mode, either orthographic or perspective", default = "perspective", choices = ["perspective", "orthographic"])
225
+ parser.add_argument("--padding", help = "Padding", type = float, default = 1.0)
226
+
227
+ # Both blender and python share the same command line arguments.
228
+ # They are separated by double dash ('--'), everything before is for blender,
229
+ # everything afterwards is for python (except for the first argument, wich is
230
+ # the program name, which is for both).
231
+ if "--" in sys.argv:
232
+ python_argv = sys.argv[sys.argv.index("--") +1:]
233
+ else:
234
+ python_argv = []
235
+
236
+ args = parser.parse_args(python_argv)
237
+
238
+ # Batoms or blender will silently set the extension to png if it's not already.
239
+ # This is surprising, so stop now before that happens.
240
+ if Path(args.output).suffix.lower() != ".png":
241
+ raise ValueError("Output location must have a .png extension")
242
+
243
+ if args.rotations is not None:
244
+ rotations = [yaml.safe_load(rotation) for rotation in args.rotations]
245
+
246
+ # Load the input data.
247
+ mol = add_molecule(
248
+ args.cube_file,
249
+ name = "molecule",
250
+ visible = True,
251
+ rotations = rotations,
252
+ isovalues = args.isovalues,
253
+ isotype = args.isotype,
254
+ primary_color = args.primary_color,
255
+ secondary_color = args.secondary_color if args.isocolor == "sign" else args.primary_color,
256
+ style = args.style
257
+ )
258
+
259
+ # Uncomment to show atom labels.
260
+ # Needs some tweaking to appear in render (viewport only by default).
261
+ #mol.show_label = 'species'
262
+
263
+ # If we have a second cube, add that too.
264
+ if args.second_cube is not None:
265
+ mol2 = add_molecule(
266
+ args.second_cube,
267
+ name = "molecule2",
268
+ visible = True,
269
+ rotations = rotations,
270
+ isovalues = args.isovalues,
271
+ isotype = args.isotype,
272
+ primary_color = args.primary_color if args.isocolor == "sign" else args.secondary_color,
273
+ secondary_color = args.secondary_color,
274
+ style = args.style
275
+ )
276
+
277
+ if args.alpha:
278
+ # Set all materials transparent.
279
+ for material in bpy.data.materials:
280
+ try:
281
+ material.node_tree.nodes['Principled BSDF'].inputs['Alpha'].default_value = args.alpha
282
+ except Exception as e:
283
+ pass
284
+
285
+
286
+ # Draw any dipoles.
287
+ if args.dipoles is not None:
288
+
289
+ dipoles = [yaml.safe_load(dipole) for dipole in args.dipoles]
290
+ for start_coord, end_coord, rgba in dipoles:
291
+ draw_arrow(start_coord, end_coord, 0.08, rgba, collection = mol.coll)
292
+
293
+
294
+ # Setup rendering settings.
295
+ # mol.render.engine = 'workbench'
296
+ # mol.render.engine = 'eevee'
297
+ mol.render.engine = 'cycles'
298
+ mol.render.resolution = [args.resolution, args.resolution]
299
+ # Set up cycles for good quality rendering.
300
+ # Prevents early end to rendering (forces us to use the actual number of samples).
301
+ bpy.context.scene.cycles.use_adaptive_sampling = False
302
+ # Quality control, more = better and slower.
303
+ bpy.context.scene.cycles.samples = args.render_samples
304
+ # Post-processing to remove noise, works well for coloured backgrounds, useless for transparency.
305
+ bpy.context.scene.cycles.use_denoising = True
306
+ # Ray-tracing options
307
+ bpy.context.scene.cycles.max_bounces = 48
308
+ bpy.context.scene.cycles.transparent_max_bounces = 24
309
+ if args.use_gpu:
310
+ bpy.context.scene.cycles.device = "GPU"
311
+
312
+ # Use maximum compression.
313
+ bpy.context.scene.render.image_settings.compression = 1000
314
+
315
+ # Change light intensity.
316
+ bpy.data.lights["batoms_light_Default"].node_tree.nodes["Emission"].inputs[1].default_value = 0.5 #0.3
317
+ #mol.render.lights["Default"].energy=10
318
+
319
+ # Change view mode.
320
+ if args.perspective == "perspective":
321
+ mol.render.camera.type = "PERSP"
322
+
323
+ else:
324
+ mol.render.camera.type = "ORTHO"
325
+
326
+ # Enable to add an outline.
327
+ #bpy.context.scene.render.use_freestyle = True
328
+
329
+ # Performance options.
330
+ bpy.context.scene.render.threads_mode = 'FIXED'
331
+ bpy.context.scene.render.threads = args.cpus
332
+
333
+ mol.get_image(viewport = args.orientation, output = args.output, padding = args.padding)
334
+ # # Move the camera.
335
+ # mol.render.camera.location = (100,0,0)
336
+ # mol.render.camera.look_at = mol.get_center_of_geometry()
337
+ # bpy.ops.object.select_all(action='DESELECT')
338
+ # for obj in mol.coll.objects[:]:
339
+ # obj.select_set(True)
340
+ # #bpy.ops.view3d.camera_to_view_selected()
341
+
342
+ return 0
343
+
344
+ # If we've been invoked as a program, call main().
345
+ if __name__ == '__main__':
346
+ try:
347
+ sys.exit(main())
348
+
349
+ except Exception as e:
350
+ logging.error("Erro", exc_info = True)
351
+ sys.exit(1)