digichem-core 6.1.0__py3-none-any.whl → 6.10.1__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 +2 -2
- digichem/config/base.py +5 -3
- digichem/data/batoms/batoms-renderer.py +190 -50
- digichem/data/batoms/batoms_renderer.py +500 -0
- digichem/file/base.py +14 -0
- digichem/file/cube.py +185 -16
- digichem/file/types.py +1 -0
- digichem/image/render.py +144 -45
- digichem/image/vmd.py +7 -2
- digichem/input/digichem_input.py +2 -2
- digichem/memory.py +10 -0
- digichem/misc/io.py +84 -1
- digichem/parse/__init__.py +6 -1
- digichem/parse/base.py +85 -54
- digichem/parse/cclib.py +103 -13
- digichem/parse/dump.py +3 -3
- digichem/parse/orca.py +1 -0
- digichem/parse/pyscf.py +25 -0
- digichem/parse/turbomole.py +3 -3
- digichem/parse/util.py +146 -65
- digichem/result/excited_state.py +17 -11
- digichem/result/metadata.py +272 -3
- digichem/result/result.py +3 -0
- digichem/result/spectroscopy.py +42 -0
- digichem/test/test_memory.py +33 -0
- digichem/test/test_parsing.py +68 -1
- digichem/test/test_result.py +1 -1
- digichem/test/util.py +2 -1
- {digichem_core-6.1.0.dist-info → digichem_core-6.10.1.dist-info}/METADATA +4 -3
- {digichem_core-6.1.0.dist-info → digichem_core-6.10.1.dist-info}/RECORD +33 -30
- {digichem_core-6.1.0.dist-info → digichem_core-6.10.1.dist-info}/WHEEL +1 -1
- {digichem_core-6.1.0.dist-info → digichem_core-6.10.1.dist-info}/licenses/COPYING.md +0 -0
- {digichem_core-6.1.0.dist-info → digichem_core-6.10.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,500 @@
|
|
|
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
|
+
|
|
11
|
+
# import debugpy
|
|
12
|
+
# debugpy.listen(5678)
|
|
13
|
+
# debugpy.wait_for_client()
|
|
14
|
+
|
|
15
|
+
import addon_utils
|
|
16
|
+
def handle_error(exception):
|
|
17
|
+
raise exception
|
|
18
|
+
addon_utils.enable("batoms", handle_error = handle_error, default_set=True)
|
|
19
|
+
|
|
20
|
+
import sys
|
|
21
|
+
import argparse
|
|
22
|
+
import itertools
|
|
23
|
+
import bpy
|
|
24
|
+
import yaml
|
|
25
|
+
import math
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
import logging
|
|
28
|
+
|
|
29
|
+
import ase.io
|
|
30
|
+
from batoms import Batoms
|
|
31
|
+
from batoms.utils.butils import object_mode
|
|
32
|
+
from batoms.render import Render
|
|
33
|
+
|
|
34
|
+
class Digichem_render(Render):
|
|
35
|
+
def set_viewport_distance_center(self, center=None, padding=0, canvas=None):
|
|
36
|
+
"""
|
|
37
|
+
Calculate canvas and direction
|
|
38
|
+
"""
|
|
39
|
+
batoms = self.batoms
|
|
40
|
+
if padding is None:
|
|
41
|
+
padding = max(batoms.size) + 0.5
|
|
42
|
+
if center is None:
|
|
43
|
+
center = batoms.get_center_of_geometry()
|
|
44
|
+
self.center = center
|
|
45
|
+
if canvas is None:
|
|
46
|
+
width, height, depth = batoms.get_canvas_box(
|
|
47
|
+
direction=self.viewport, padding=padding
|
|
48
|
+
)
|
|
49
|
+
else:
|
|
50
|
+
width = canvas[0]
|
|
51
|
+
height = canvas[1]
|
|
52
|
+
depth = canvas[2]
|
|
53
|
+
if self.distance < 0:
|
|
54
|
+
self.distance = max(10, depth)
|
|
55
|
+
|
|
56
|
+
self.update_camera(width, height, depth / 2)
|
|
57
|
+
|
|
58
|
+
# To auto centre the camera, we need to select the molecule as well as all isosurfaces that might be present.
|
|
59
|
+
# Select the molecule.
|
|
60
|
+
self.batoms.obj.select_set(True)
|
|
61
|
+
|
|
62
|
+
# Isosurfaces.
|
|
63
|
+
for obj in self.batoms.coll.all_objects:
|
|
64
|
+
if obj.batoms.type == "ISOSURFACE":
|
|
65
|
+
obj.select_set(True)
|
|
66
|
+
|
|
67
|
+
# Set camera as active.
|
|
68
|
+
bpy.context.scene.camera = self.camera.obj
|
|
69
|
+
|
|
70
|
+
# Manually set a focal point.
|
|
71
|
+
self.camera.lens = 50
|
|
72
|
+
# Auto centre.
|
|
73
|
+
bpy.ops.view3d.camera_to_view_selected()
|
|
74
|
+
|
|
75
|
+
self.update_light()
|
|
76
|
+
|
|
77
|
+
def add_molecule(
|
|
78
|
+
cube_file,
|
|
79
|
+
name,
|
|
80
|
+
rotations = None,
|
|
81
|
+
visible = True,
|
|
82
|
+
isovalues = None,
|
|
83
|
+
isotype = "both",
|
|
84
|
+
primary_color = [1, 0.058, 0.0, 0.55],
|
|
85
|
+
secondary_color = [0.1, 0.1, 0.9, 0.55],
|
|
86
|
+
style = "default"
|
|
87
|
+
):
|
|
88
|
+
"""
|
|
89
|
+
"""
|
|
90
|
+
rotations = [] if rotations is None else rotations
|
|
91
|
+
isovalues = [] if isovalues is None else isovalues
|
|
92
|
+
|
|
93
|
+
surface_settings = []
|
|
94
|
+
for isovalue in isovalues:
|
|
95
|
+
if isotype in ["both", "positive"]:
|
|
96
|
+
surface_settings.append({'level': isovalue, 'color': primary_color, 'material_style': style})
|
|
97
|
+
|
|
98
|
+
if isotype in ["both", "negative"]:
|
|
99
|
+
surface_settings.append({'level': -isovalue, 'color': secondary_color, 'material_style': style})
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
# Load the input data.
|
|
103
|
+
cube = ase.io.read(cube_file, format="cube", read_data=True, full_output=True)
|
|
104
|
+
|
|
105
|
+
# The centre of the cube is often offset, fix that by shifting the atoms.
|
|
106
|
+
cube["atoms"].translate(-cube["origin"][0:3])
|
|
107
|
+
|
|
108
|
+
# Get the mol object.
|
|
109
|
+
mol = Batoms(name, from_ase = cube["atoms"])
|
|
110
|
+
|
|
111
|
+
# Set some look and feel options.
|
|
112
|
+
|
|
113
|
+
# Hide cell boundaries.
|
|
114
|
+
mol.cell.hide = True
|
|
115
|
+
|
|
116
|
+
# Colour tuning.
|
|
117
|
+
# Carbon to 'black'.
|
|
118
|
+
new_colors = {
|
|
119
|
+
"C": (0.095, 0.095, 0.095, 1),
|
|
120
|
+
"B": (1.0, 0.396, 0.468, 1)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
for atom, color in new_colors.items():
|
|
124
|
+
try:
|
|
125
|
+
mol[atom].color = color
|
|
126
|
+
except AttributeError:
|
|
127
|
+
pass
|
|
128
|
+
|
|
129
|
+
# And bonds.
|
|
130
|
+
for key, value in mol.bond.settings.items():
|
|
131
|
+
for atom, color in new_colors.items():
|
|
132
|
+
if key[0] == atom:
|
|
133
|
+
value['color1'] = color
|
|
134
|
+
|
|
135
|
+
if key[1] == "C":
|
|
136
|
+
value['color2'] = color
|
|
137
|
+
|
|
138
|
+
# Change molecule style.
|
|
139
|
+
mol.model_style = 1
|
|
140
|
+
|
|
141
|
+
# Slightly increase volume of all atoms.
|
|
142
|
+
for atom in mol.species.keys():
|
|
143
|
+
mol[atom].scale *= 1.25
|
|
144
|
+
|
|
145
|
+
# Increase volume of H atoms
|
|
146
|
+
mol['H'].scale = 0.75
|
|
147
|
+
|
|
148
|
+
# Add volumes.
|
|
149
|
+
if len(surface_settings) != 0:
|
|
150
|
+
mol.volumetric_data['surface'] = cube['data']
|
|
151
|
+
|
|
152
|
+
for index, settings in enumerate(surface_settings):
|
|
153
|
+
mol.isosurface.settings[index+1] = settings
|
|
154
|
+
|
|
155
|
+
mol.isosurface.draw()
|
|
156
|
+
|
|
157
|
+
# Now move the entire molecule (isosurface and all) back to the origin.
|
|
158
|
+
mol.obj.select_set(True)
|
|
159
|
+
bpy.ops.object.origin_set(type="ORIGIN_GEOMETRY", center='MEDIAN')
|
|
160
|
+
bpy.context.object.location = [0,0,0]
|
|
161
|
+
|
|
162
|
+
# If we have any rotations, apply those.
|
|
163
|
+
for axis, angle in rotations:
|
|
164
|
+
# Convert to degree.
|
|
165
|
+
# Not used.
|
|
166
|
+
degree = angle * (180/math.pi)
|
|
167
|
+
|
|
168
|
+
if axis == 0:
|
|
169
|
+
axis = "x"
|
|
170
|
+
|
|
171
|
+
elif axis == 1:
|
|
172
|
+
axis = "y"
|
|
173
|
+
|
|
174
|
+
elif axis == 2:
|
|
175
|
+
axis = "z"
|
|
176
|
+
|
|
177
|
+
else:
|
|
178
|
+
raise ValueError("Unknown rotation axis '{}'".format(axis))
|
|
179
|
+
|
|
180
|
+
#mol.rotate(degree, axis)
|
|
181
|
+
|
|
182
|
+
# Taken from batoms source (collection.py: 51)
|
|
183
|
+
object_mode()
|
|
184
|
+
bpy.ops.object.select_all(action='DESELECT')
|
|
185
|
+
mol.obj.select_set(True)
|
|
186
|
+
bpy.context.view_layer.objects.active = mol.obj
|
|
187
|
+
bpy.ops.transform.rotate(value=angle, orient_axis=axis.upper(),
|
|
188
|
+
center_override = (0,0,0))
|
|
189
|
+
|
|
190
|
+
if not visible:
|
|
191
|
+
mol.hide = True
|
|
192
|
+
|
|
193
|
+
return mol
|
|
194
|
+
|
|
195
|
+
# Adapted from https://blender.stackexchange.com/questions/5898/how-can-i-create-a-cylinder-linking-two-points-with-python?newreg=f372ba9448694f5b97879a6dab963cee
|
|
196
|
+
def draw_primitive(start, end, radius, mesh_type, color, collection = None):
|
|
197
|
+
"""
|
|
198
|
+
"""
|
|
199
|
+
dx = end[0] - start[0]
|
|
200
|
+
dy = end[1] - start[1]
|
|
201
|
+
dz = end[2] - start[2]
|
|
202
|
+
dist = math.sqrt(dx**2 + dy**2 + dz**2)
|
|
203
|
+
|
|
204
|
+
# First draw the shape.
|
|
205
|
+
if mesh_type == "cylinder":
|
|
206
|
+
bpy.ops.mesh.primitive_cylinder_add(
|
|
207
|
+
radius = radius,
|
|
208
|
+
depth = dist,
|
|
209
|
+
vertices = 32,
|
|
210
|
+
location = (dx/2 + start[0], dy/2 + start[1], dz/2 + start[2])
|
|
211
|
+
)
|
|
212
|
+
elif mesh_type == "cone":
|
|
213
|
+
bpy.ops.mesh.primitive_cone_add(
|
|
214
|
+
radius1 = radius,
|
|
215
|
+
depth = dist,
|
|
216
|
+
vertices = 32,
|
|
217
|
+
location = (dx/2 + start[0], dy/2 + start[1], dz/2 + start[2])
|
|
218
|
+
)
|
|
219
|
+
else:
|
|
220
|
+
raise ValueError("Unknown mesh type '{}'".format(mesh_type))
|
|
221
|
+
|
|
222
|
+
# Get a reference to the object we just made.
|
|
223
|
+
obj = bpy.context.active_object
|
|
224
|
+
|
|
225
|
+
phi = math.atan2(dy, dx)
|
|
226
|
+
theta = math.acos(dz/dist)
|
|
227
|
+
|
|
228
|
+
bpy.context.object.rotation_euler[1] = theta
|
|
229
|
+
bpy.context.object.rotation_euler[2] = phi
|
|
230
|
+
|
|
231
|
+
# Get material
|
|
232
|
+
mat = bpy.data.materials.new(name="Material")
|
|
233
|
+
|
|
234
|
+
# assign to 1st material slot
|
|
235
|
+
obj.active_material = mat
|
|
236
|
+
|
|
237
|
+
mat.use_nodes = True
|
|
238
|
+
tree = mat.node_tree
|
|
239
|
+
nodes = tree.nodes
|
|
240
|
+
bsdf = nodes["Principled BSDF"]
|
|
241
|
+
bsdf.inputs["Base Color"].default_value = color
|
|
242
|
+
bsdf.inputs["Metallic"].default_value = 0.1
|
|
243
|
+
try:
|
|
244
|
+
# Blener 3.x
|
|
245
|
+
bsdf.inputs["Specular"].default_value = 0.2
|
|
246
|
+
except KeyError:
|
|
247
|
+
# Blender 4.x
|
|
248
|
+
bsdf.inputs["Specular IOR Level"].default_value = 0.2
|
|
249
|
+
|
|
250
|
+
bsdf.inputs["Roughness"].default_value = 0.2
|
|
251
|
+
mat.diffuse_color = color
|
|
252
|
+
|
|
253
|
+
# If we've been asked to, asign our new object to a given collection.
|
|
254
|
+
# First unlink from any old collections
|
|
255
|
+
for coll in obj.users_collection:
|
|
256
|
+
# Unlink the object
|
|
257
|
+
coll.objects.unlink(obj)
|
|
258
|
+
|
|
259
|
+
# Link each object to the target collection
|
|
260
|
+
collection.objects.link(obj)
|
|
261
|
+
|
|
262
|
+
return obj
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def draw_arrow(start, end, radius, color, split = 0.9, collection = None):
|
|
266
|
+
# Decide what proportion of the total vector to dedicate to the arrow stem and head.
|
|
267
|
+
dx = end[0] - start[0]
|
|
268
|
+
dy = end[1] - start[1]
|
|
269
|
+
dz = end[2] - start[2]
|
|
270
|
+
dist = math.sqrt(dx**2 + dy**2 + dz**2)
|
|
271
|
+
|
|
272
|
+
join = (dx * split + start[0], dy * split + start[1], dz * split + start[2])
|
|
273
|
+
cylinder = draw_primitive(start, join, radius, "cylinder", color, collection = collection)
|
|
274
|
+
cone = draw_primitive(join, end, radius*2, "cone", color, collection = collection)
|
|
275
|
+
|
|
276
|
+
# Select the two objects and join them together.
|
|
277
|
+
bpy.ops.object.select_all(action='DESELECT')
|
|
278
|
+
cylinder.select_set(True)
|
|
279
|
+
cone.select_set(True)
|
|
280
|
+
bpy.ops.object.join()
|
|
281
|
+
|
|
282
|
+
arrow = cone
|
|
283
|
+
|
|
284
|
+
# Set the origin of the new combined object to the origin of the arrow.
|
|
285
|
+
bpy.context.scene.cursor.location = start
|
|
286
|
+
bpy.ops.object.origin_set(type='ORIGIN_CURSOR')
|
|
287
|
+
|
|
288
|
+
return arrow
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def main():
|
|
293
|
+
parser = argparse.ArgumentParser(
|
|
294
|
+
prog='Beautiful Atoms Renderer',
|
|
295
|
+
description='Render images with BAtoms')
|
|
296
|
+
|
|
297
|
+
parser.add_argument("cube_file", help = "Path to the cube file to read")
|
|
298
|
+
parser.add_argument("output", help = "File to write to", nargs="?", default = None)
|
|
299
|
+
parser.add_argument("--second_cube", help = "Optional second cube file to read additional isosurface data from", default = None)
|
|
300
|
+
parser.add_argument("--isovalues", help = "List of isovalues to render", nargs = "*", type = float, default = [])
|
|
301
|
+
parser.add_argument("--isotype", help = "Whether to render positive, negative or both isosurfaces for each isovalue", choices = ["positive", "negative", "both"], default = "both")
|
|
302
|
+
parser.add_argument("--isocolor", help = "The colouring method to use for isosurfaces", choices = ["sign", "cube"], default = "sign")
|
|
303
|
+
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.5])
|
|
304
|
+
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.5])
|
|
305
|
+
parser.add_argument("--style", help = "Material style for isosurfaces", choices = ('default', 'metallic', 'plastic', 'ceramic', 'mirror'), default = "ceramic")
|
|
306
|
+
parser.add_argument("--cpus", help = "Number of parallel CPUs to use for rendering", type = int, default = 1)
|
|
307
|
+
parser.add_argument("--use-gpu", help = "Whether to enable GPU rendering", action = "store_true")
|
|
308
|
+
parser.add_argument("--orientation", help = "The orientation to render from, as x, y, z values", nargs = 3, type = float, default = [0, 0, 0])
|
|
309
|
+
parser.add_argument("--resolution", help = "The output resolution in px", type = int, default = 1024)
|
|
310
|
+
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 = 64)# default = 256)
|
|
311
|
+
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 = [])
|
|
312
|
+
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 = [])
|
|
313
|
+
parser.add_argument("--alpha", help = "Override the opacity value for all molecule objects (but not dipoles) to 1- this value, useful for showing dipole arrows more clearly", default = None, type = float)
|
|
314
|
+
parser.add_argument("--perspective", help = "The perspective mode, either orthographic or perspective", default = "perspective", choices = ["perspective", "orthographic"])
|
|
315
|
+
parser.add_argument("--padding", help = "Padding", type = float, default = 1.0)
|
|
316
|
+
parser.add_argument("--multi", help = "Render multiple images, each of a different angle of the scene. Each argument should consist of 6 parts, the x y z position, the resolution, the samples, and the filename (which is appended to 'output')", nargs = 6, default =[], action="append")
|
|
317
|
+
# Both blender and python share the same command line arguments.
|
|
318
|
+
# They are separated by double dash ('--'), everything before is for blender,
|
|
319
|
+
# everything afterwards is for python (except for the first argument, wich is
|
|
320
|
+
# the program name, which is for both).
|
|
321
|
+
if "--" in sys.argv:
|
|
322
|
+
python_argv = sys.argv[sys.argv.index("--") +1:]
|
|
323
|
+
else:
|
|
324
|
+
python_argv = []
|
|
325
|
+
|
|
326
|
+
args = parser.parse_args(python_argv)
|
|
327
|
+
|
|
328
|
+
# Batoms or blender will silently set the extension to png if it's not already.
|
|
329
|
+
# This is surprising, so stop now before that happens.
|
|
330
|
+
if args.output is not None and Path(args.output).suffix.lower() != ".png":
|
|
331
|
+
raise ValueError("Output location must have a .png extension")
|
|
332
|
+
|
|
333
|
+
if args.rotations is not None:
|
|
334
|
+
rotations = [yaml.safe_load(rotation) for rotation in args.rotations]
|
|
335
|
+
|
|
336
|
+
if args.multi != []:
|
|
337
|
+
if args.orientation != [0, 0, 0]:
|
|
338
|
+
raise ValueError("You cannot set both --orientation and --multi!")
|
|
339
|
+
|
|
340
|
+
if args.resolution != 1024:
|
|
341
|
+
raise ValueError("You cannot set both --resolution and --multi!")
|
|
342
|
+
|
|
343
|
+
if args.output is not None:
|
|
344
|
+
raise ValueError("You cannot set both 'output' and --multi!")
|
|
345
|
+
|
|
346
|
+
# Remove the starting cube object.
|
|
347
|
+
bpy.ops.object.select_all(action='SELECT')
|
|
348
|
+
bpy.ops.object.delete()
|
|
349
|
+
|
|
350
|
+
# Load the input data.
|
|
351
|
+
mol = add_molecule(
|
|
352
|
+
args.cube_file,
|
|
353
|
+
name = "first_mol",
|
|
354
|
+
visible = True,
|
|
355
|
+
rotations = rotations,
|
|
356
|
+
isovalues = args.isovalues,
|
|
357
|
+
isotype = args.isotype,
|
|
358
|
+
primary_color = args.primary_color,
|
|
359
|
+
secondary_color = args.secondary_color if args.isocolor == "sign" else args.primary_color,
|
|
360
|
+
style = args.style
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
# Uncomment to show atom labels.
|
|
364
|
+
# Needs some tweaking to appear in render (viewport only by default).
|
|
365
|
+
#mol.show_label = 'species'
|
|
366
|
+
mol2 = None
|
|
367
|
+
|
|
368
|
+
# If we have a second cube, add that too.
|
|
369
|
+
if args.second_cube is not None:
|
|
370
|
+
mol2 = add_molecule(
|
|
371
|
+
args.second_cube,
|
|
372
|
+
name = "second_mol",
|
|
373
|
+
visible = False,
|
|
374
|
+
rotations = rotations,
|
|
375
|
+
isovalues = args.isovalues,
|
|
376
|
+
isotype = args.isotype,
|
|
377
|
+
primary_color = args.primary_color if args.isocolor == "sign" else args.secondary_color,
|
|
378
|
+
secondary_color = args.secondary_color,
|
|
379
|
+
style = args.style
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
if args.alpha:
|
|
383
|
+
# Set all materials transparent.
|
|
384
|
+
for material in bpy.data.materials:
|
|
385
|
+
try:
|
|
386
|
+
material.node_tree.nodes['Principled BSDF'].inputs['Alpha'].default_value = (1 - args.alpha)
|
|
387
|
+
except Exception as e:
|
|
388
|
+
pass
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
# Draw any dipoles.
|
|
392
|
+
arrows = []
|
|
393
|
+
if args.dipoles is not None:
|
|
394
|
+
|
|
395
|
+
dipoles = [yaml.safe_load(dipole) for dipole in args.dipoles]
|
|
396
|
+
for start_coord, end_coord, rgba in dipoles:
|
|
397
|
+
arrows.append(draw_arrow(start_coord, end_coord, 0.1, rgba, collection = mol.coll))
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
# Setup rendering settings.
|
|
401
|
+
# mol.render.engine = 'workbench'
|
|
402
|
+
# mol.render.engine = 'eevee'
|
|
403
|
+
mol.render.engine = 'cycles'
|
|
404
|
+
# Set up cycles for good quality rendering.
|
|
405
|
+
# Prevents early end to rendering (forces us to use the actual number of samples).
|
|
406
|
+
bpy.context.scene.cycles.use_adaptive_sampling = False
|
|
407
|
+
# Post-processing to remove noise, works well for coloured backgrounds, useless for transparency.
|
|
408
|
+
bpy.context.scene.cycles.use_denoising = True
|
|
409
|
+
# Ray-tracing options
|
|
410
|
+
bpy.context.scene.cycles.max_bounces = 48
|
|
411
|
+
bpy.context.scene.cycles.transparent_max_bounces = 24
|
|
412
|
+
if args.use_gpu:
|
|
413
|
+
bpy.context.scene.cycles.device = "GPU"
|
|
414
|
+
|
|
415
|
+
# Use maximum compression.
|
|
416
|
+
bpy.context.scene.render.image_settings.compression = 1000
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
# Change light intensity.
|
|
420
|
+
mol.render.lights["Default"].direction = [0.1, 0.1, 1]
|
|
421
|
+
mol.render.lights["Default"].obj.data.node_tree.nodes["Emission"].inputs[1].default_value = 0.2
|
|
422
|
+
mol.render.lights["Default"].obj.data.angle = 0.174533
|
|
423
|
+
|
|
424
|
+
# Add a second light for depth.
|
|
425
|
+
mol.render.lights.add("Accent1", direction = [1,0.5,0.75])
|
|
426
|
+
mol.render.lights.add("Accent2", direction = [0.5,1,0.75])
|
|
427
|
+
|
|
428
|
+
mol.render.lights["Accent1"].obj.data.angle = 0.0872665
|
|
429
|
+
mol.render.lights["Accent1"].obj.data.node_tree.nodes["Emission"].inputs[1].default_value = 0.25
|
|
430
|
+
mol.render.lights["Accent2"].obj.data.angle = 0.0872665
|
|
431
|
+
mol.render.lights["Accent2"].obj.data.node_tree.nodes["Emission"].inputs[1].default_value = 0.25
|
|
432
|
+
|
|
433
|
+
# bpy.data.lights["batoms_light_Default"].node_tree.nodes["Emission"].inputs[1].default_value = 0.45
|
|
434
|
+
# bpy.data.lights["batoms_light_Default"].angle
|
|
435
|
+
#mol.render.lights["Default"].energy=10
|
|
436
|
+
|
|
437
|
+
# Change view mode.
|
|
438
|
+
if args.perspective == "perspective":
|
|
439
|
+
mol.render.camera.type = "PERSP"
|
|
440
|
+
|
|
441
|
+
else:
|
|
442
|
+
mol.render.camera.type = "ORTHO"
|
|
443
|
+
|
|
444
|
+
# Enable to add an outline.
|
|
445
|
+
#bpy.context.scene.render.use_freestyle = True
|
|
446
|
+
|
|
447
|
+
# We have plenty of memory to play with, use one tile.
|
|
448
|
+
bpy.context.scene.cycles.tile_x = args.resolution
|
|
449
|
+
bpy.context.scene.cycles.tile_y = args.resolution
|
|
450
|
+
bpy.context.scene.cycles.tile_size = args.resolution
|
|
451
|
+
|
|
452
|
+
# Performance options.
|
|
453
|
+
bpy.context.scene.render.threads_mode = 'FIXED'
|
|
454
|
+
bpy.context.scene.render.threads = args.cpus
|
|
455
|
+
|
|
456
|
+
# Set our custom renderer so we can modify zoom etc.
|
|
457
|
+
mol.render = Digichem_render()
|
|
458
|
+
|
|
459
|
+
# We have two ways we can change which angle we render from.
|
|
460
|
+
# 1) the viewport keyword arg (which places the camera in a certain location).
|
|
461
|
+
# 2) rotate the molecule.
|
|
462
|
+
#
|
|
463
|
+
# We use option 2, because this gives us more control.
|
|
464
|
+
|
|
465
|
+
# Work out how many angles we're rendering from.
|
|
466
|
+
if args.multi == []:
|
|
467
|
+
# Just one.
|
|
468
|
+
targets = [[args.orientation[0], args.orientation[1], args.orientation[2], args.resolution, args.render_samples, args.output]]
|
|
469
|
+
|
|
470
|
+
else:
|
|
471
|
+
# More than one.
|
|
472
|
+
targets = args.multi
|
|
473
|
+
|
|
474
|
+
for x, y, z, resolution, samples, full_file_name in targets:
|
|
475
|
+
# Add args.output and mini_file_name together (useful for --multi).
|
|
476
|
+
orientation = (float(x), float(y), float(z))
|
|
477
|
+
|
|
478
|
+
mol.render.resolution = [resolution, resolution]
|
|
479
|
+
# Quality control, more = better and slower.
|
|
480
|
+
bpy.context.scene.cycles.samples = int(samples)
|
|
481
|
+
mol.obj.delta_rotation_euler = orientation
|
|
482
|
+
|
|
483
|
+
if mol2 is not None:
|
|
484
|
+
mol2.obj.delta_rotation_euler = orientation
|
|
485
|
+
|
|
486
|
+
for arrow in arrows:
|
|
487
|
+
arrow.delta_rotation_euler = orientation
|
|
488
|
+
|
|
489
|
+
mol.get_image(viewport = [0,0,1], output = full_file_name, padding = args.padding)
|
|
490
|
+
|
|
491
|
+
return 0
|
|
492
|
+
|
|
493
|
+
# If we've been invoked as a program, call main().
|
|
494
|
+
if __name__ == '__main__':
|
|
495
|
+
try:
|
|
496
|
+
sys.exit(main())
|
|
497
|
+
|
|
498
|
+
except Exception as e:
|
|
499
|
+
logging.error("Erro", exc_info = True)
|
|
500
|
+
sys.exit(1)
|
digichem/file/base.py
CHANGED
|
@@ -45,6 +45,20 @@ class File_maker_ABC():
|
|
|
45
45
|
"""
|
|
46
46
|
raise NotImplementedError("delete() is not implemented in this ABC")
|
|
47
47
|
|
|
48
|
+
def get_parallel(self, name = 'file', cpus = 1):
|
|
49
|
+
"""
|
|
50
|
+
Generate the file represented by this object in a parallel context.
|
|
51
|
+
|
|
52
|
+
get_parallel() will be called by a higher level function as an argument to ThreadPoolExecutor.map() or similar,
|
|
53
|
+
to generate many files simultaneously. This is useful for slow operations (such as cube generation) that are
|
|
54
|
+
difficult to parallelise individually, but easy to parallelise across multiple files.
|
|
55
|
+
|
|
56
|
+
:param name: The file to generate.
|
|
57
|
+
:param cpus: The number of CPUs this operation should use, nearly always 1.
|
|
58
|
+
"""
|
|
59
|
+
# This default implementation does nothing.
|
|
60
|
+
pass
|
|
61
|
+
|
|
48
62
|
#################################
|
|
49
63
|
# Implemented in this sub-class #
|
|
50
64
|
#################################
|