Newer
Older
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
FLAG_s2 = True
break
s2 += 1
# If the vertex (atom) is not yet in the vertex list:
# append the number of atom and the vertex to the two lists.
# For the first atom:
if FLAG_s1 == False:
atom1 = copy(all_atoms[stick.atom1-1].location)
stick_vertices.append(atom1)
stick_vertices_nr.append(stick.atom1-1)
# For the second atom:
if FLAG_s2 == False:
atom2 = copy(all_atoms[stick.atom2-1].location)
stick_vertices.append(atom2)
stick_vertices_nr.append(stick.atom2-1)
# Build the edges:
# If both vertices (atoms) were not in the lists, then
# the edge is simply [i,i+1]. These are two new vertices
# (atoms), so increase i by 2.
if FLAG_s1 == False and FLAG_s2 == False:
stick_edges.append([i,i+1])
i += 2
# Both vertices (atoms) were already in the list, so then
# use the vertices (atoms), which already exist. They are
# at positions s1 and s2.
if FLAG_s1 == True and FLAG_s2 == True:
stick_edges.append([s1,s2])
# The following two if cases describe the situation that
# only one vertex (atom) was in the list. Since only ONE
# new vertex was added, increase i by one.
if FLAG_s1 == True and FLAG_s2 == False:
stick_edges.append([s1,i])
i += 1
if FLAG_s1 == False and FLAG_s2 == True:
stick_edges.append([i,s2])
i += 1
# Build the mesh of the sticks
stick_mesh = bpy.data.meshes.new("Mesh_sticks")
stick_mesh.from_pydata(stick_vertices, stick_edges, [])
stick_mesh.update()
new_stick_mesh = bpy.data.objects.new("Sticks", stick_mesh)
# Link the active mesh to the molecule collection
coll_molecule.objects.link(new_stick_mesh)
# Apply the skin modifier.
new_stick_mesh.modifiers.new(name="Sticks_skin", type='SKIN')
# Smooth the skin surface if this option has been chosen.
new_stick_mesh.modifiers[0].use_smooth_shade = use_sticks_smooth
# Apply the Subdivision modifier.
new_stick_mesh.modifiers.new(name="Sticks_subsurf", type='SUBSURF')
# Options: choose the levels
new_stick_mesh.modifiers[1].levels = sticks_subdiv_view
new_stick_mesh.modifiers[1].render_levels = sticks_subdiv_render
stick_material = bpy.data.materials.new(ELEMENTS[-1].name)
stick_material.diffuse_color = ELEMENTS[-1].color
new_stick_mesh.active_material = stick_material
# This is for putting the radius of the sticks onto
# the desired value 'Stick_diameter'
bpy.context.view_layer.objects.active = new_stick_mesh
# EDIT mode
bpy.ops.object.mode_set(mode='EDIT', toggle=False)
bm = bmesh.from_edit_mesh(new_stick_mesh.data)
bpy.ops.mesh.select_all(action='DESELECT')
# Select all vertices
for v in bm.verts:
v.select = True
# This is somewhat a factor for the radius.
r_f = 4.0
# Apply operator 'skin_resize'.
bpy.ops.transform.skin_resize(
value=(
Stick_diameter * r_f,
Stick_diameter * r_f,
Stick_diameter * r_f,
),
constraint_axis=(False, False, False),
orient_type='GLOBAL',
mirror=False,
use_proportional_edit=False,
snap=False,
snap_target='CLOSEST',
snap_point=(0, 0, 0),
snap_align=False,
snap_normal=(0, 0, 0),
release_confirm=False,
)
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
# Back to the OBJECT mode.
bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
return new_stick_mesh
# Draw the sticks the normal way: connect the atoms by simple cylinders.
# Two options: 1. single cylinders parented to an empty
# 2. one single mesh object
def draw_sticks_normal(all_atoms,
all_sticks,
center,
Stick_diameter,
Stick_sectors,
use_sticks_smooth,
use_sticks_one_object,
use_sticks_one_object_nr,
coll_molecule):
stick_material = bpy.data.materials.new(ELEMENTS[-1].name)
stick_material.diffuse_color = ELEMENTS[-1].color
up_axis = Vector([0.0, 0.0, 1.0])
# For all sticks, do ...
list_group = []
list_group_sub = []
counter = 0
for stick in all_sticks:
# The vectors of the two atoms
atom1 = all_atoms[stick.atom1-1].location-center
atom2 = all_atoms[stick.atom2-1].location-center
# Location
location = (atom1 + atom2) * 0.5
# The difference of both vectors
v = (atom2 - atom1)
# Angle with respect to the z-axis
angle = v.angle(up_axis, 0)
# Cross-product between v and the z-axis vector. It is the
# vector of rotation.
axis = up_axis.cross(v)
# Calculate Euler angles
euler = Matrix.Rotation(angle, 4, axis).to_euler()
# Create stick
stick = bpy.ops.mesh.primitive_cylinder_add(vertices=Stick_sectors,
radius=Stick_diameter,
depth=v.length,
end_fill_type='NGON',
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
enter_editmode=False,
location=location,
rotation=(0, 0, 0))
# Put the stick into the scene ...
stick = bpy.context.view_layer.objects.active
# ... and rotate the stick.
stick.rotation_euler = euler
# ... and name
stick.name = "Stick_Cylinder"
counter += 1
# Smooth the cylinder.
if use_sticks_smooth == True:
bpy.ops.object.select_all(action='DESELECT')
stick.select_set(True)
bpy.ops.object.shade_smooth()
list_group_sub.append(stick)
if use_sticks_one_object == True:
if counter == use_sticks_one_object_nr:
bpy.ops.object.select_all(action='DESELECT')
for stick in list_group_sub:
stick.select_set(True)
bpy.ops.object.join()
list_group.append(bpy.context.view_layer.objects.active)
bpy.ops.object.select_all(action='DESELECT')
list_group_sub = []
counter = 0
else:
# Material ...
stick.active_material = stick_material
if use_sticks_one_object == True:
bpy.ops.object.select_all(action='DESELECT')
for stick in list_group_sub:
stick.select_set(True)
bpy.ops.object.join()
list_group.append(bpy.context.view_layer.objects.active)
bpy.ops.object.select_all(action='DESELECT')
for group in list_group:
group.select_set(True)
bpy.ops.object.join()
bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY',
center='MEDIAN')
sticks = bpy.context.view_layer.objects.active
sticks.active_material = stick_material
sticks.location += center
# Collections
# ===========
# Note the collection where the sticks were placed into.
coll_all = sticks.users_collection
if len(coll_all) > 0:
coll_past = coll_all[0]
else:
coll_past = bpy.context.scene.collection
# Link the sticks with the collection of the molecule ...
coll_molecule.objects.link(sticks)
# ... and unlink them from the collection it has been before.
coll_past.objects.unlink(sticks)
return sticks
else:
# Here we use an empty ...
bpy.ops.object.empty_add(type='ARROWS',
location=(0, 0, 0),
rotation=(0, 0, 0))
sticks_empty = bpy.context.view_layer.objects.active
sticks_empty.name = "A_sticks_empty"
# ... that is parent to all sticks. With this, we can better move
# all sticks if necessary.
for stick in list_group_sub:
stick.parent = sticks_empty
sticks_empty.location += center
# Collections
# ===========
# Create a collection that will contain all sticks + the empty and ...
coll = bpy.data.collections.new("Sticks")
# ... link it to the collection, which contains all parts of the
# molecule.
coll_molecule.children.link(coll)
# Now, create a collection that only contains the sticks and ...
coll_cylinder = bpy.data.collections.new("Sticks_cylinders")
# ... link it to the collection, which contains the sticks and empty.
coll.children.link(coll_cylinder)
# Note the collection where the empty was placed into, ...
coll_all = sticks_empty.users_collection
if len(coll_all) > 0:
coll_past = coll_all[0]
else:
coll_past = bpy.context.scene.collection
# ... link the empty with the new collection ...
coll.objects.link(sticks_empty)
# ... and unlink it from the old collection where it has been before.
coll_past.objects.unlink(sticks_empty)
# Note the collection where the cylinders were placed into, ...
coll_all = list_group_sub[0].users_collection
if len(coll_all) > 0:
coll_past = coll_all[0]
else:
coll_past = bpy.context.scene.collection
for stick in list_group_sub:
# ... link each stick with the new collection ...
coll_cylinder.objects.link(stick)
# ... and unlink it from the old collection.
coll_past.objects.unlink(stick)
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
return sticks_empty
# -----------------------------------------------------------------------------
# The main routine
def import_pdb(Ball_type,
Ball_azimuth,
Ball_zenith,
Ball_radius_factor,
radiustype,
Ball_distance_factor,
use_sticks,
use_sticks_type,
sticks_subdiv_view,
sticks_subdiv_render,
use_sticks_color,
use_sticks_smooth,
use_sticks_bonds,
use_sticks_one_object,
use_sticks_one_object_nr,
Stick_unit, Stick_dist,
Stick_sectors,
Stick_diameter,
put_to_center,
use_camera,
use_light,
filepath_pdb):
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
# List of materials
atom_material_list = []
# A list of ALL objects which are loaded (needed for selecting the loaded
# structure.
atom_object_list = []
# ------------------------------------------------------------------------
# INITIALIZE THE ELEMENT LIST
read_elements()
# ------------------------------------------------------------------------
# READING DATA OF ATOMS
(Number_of_total_atoms, all_atoms) = read_pdb_file(filepath_pdb, radiustype)
# ------------------------------------------------------------------------
# MATERIAL PROPERTIES FOR ATOMS
# The list that contains info about all types of atoms is created
# here. It is used for building the material properties for
# instance (see below).
atom_all_types_list = []
for atom in all_atoms:
FLAG_FOUND = False
for atom_type in atom_all_types_list:
# If the atom name is already in the list, FLAG on 'True'.
if atom_type[0] == atom.name:
FLAG_FOUND = True
break
# No name in the current list has been found? => New entry.
if FLAG_FOUND == False:
# Stored are: Atom label (e.g. 'Na'), the corresponding atom
# name (e.g. 'Sodium') and its color.
atom_all_types_list.append([atom.name, atom.element, atom.color])
# The list of materials is built.
# Note that all atoms of one type (e.g. all hydrogens) get only ONE
# material! This is good because then, by activating one atom in the
# Blender scene and changing the color of this atom, one changes the color
# of ALL atoms of the same type at the same time.
# Create first a new list of materials for each type of atom
# (e.g. hydrogen)
for atom_type in atom_all_types_list:
material = bpy.data.materials.new(atom_type[1])
material.name = atom_type[0]
material.diffuse_color = atom_type[2]
atom_material_list.append(material)
# Now, we go through all atoms and give them a material. For all atoms ...
for atom in all_atoms:
# ... and all materials ...
for material in atom_material_list:
# ... select the correct material for the current atom via
# comparison of names ...
if atom.name in material.name:
# ... and give the atom its material properties.
# However, before we check, if it is a vacancy, because then it
# gets some additional preparation. The vacancy is represented
# by a transparent cube.
if atom.name == "Vacancy":
# Some properties for eevee.
material.metallic = 0.8
material.specular_intensity = 0.5
material.roughness = 0.3
material.blend_method = 'OPAQUE'
material.show_transparent_back = False
# Some properties for cycles
material.use_nodes = True
mat_P_BSDF = material.node_tree.nodes['Principled BSDF']
mat_P_BSDF.inputs['Metallic'].default_value = 0.1
mat_P_BSDF.inputs['Roughness'].default_value = 0.2
mat_P_BSDF.inputs['Transmission'].default_value = 0.97
mat_P_BSDF.inputs['IOR'].default_value = 0.8
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
# The atom gets its properties.
atom.material = material
# ------------------------------------------------------------------------
# READING DATA OF STICKS
all_sticks = read_pdb_file_sticks(filepath_pdb,
use_sticks_bonds,
all_atoms)
#
# So far, all atoms, sticks and materials have been registered.
#
# ------------------------------------------------------------------------
# TRANSLATION OF THE STRUCTURE TO THE ORIGIN
# It may happen that the structure in a PDB file already has an offset
# If chosen, the structure is first put into the center of the scene
# (the offset is subtracted).
if put_to_center == True:
sum_vec = Vector((0.0,0.0,0.0))
# Sum of all atom coordinates
sum_vec = sum([atom.location for atom in all_atoms], sum_vec)
# Then the average is taken
sum_vec = sum_vec / Number_of_total_atoms
# After, for each atom the center of gravity is subtracted
for atom in all_atoms:
atom.location -= sum_vec
# ------------------------------------------------------------------------
# SCALING
# Take all atoms and adjust their radii and scale the distances.
for atom in all_atoms:
atom.location *= Ball_distance_factor
# ------------------------------------------------------------------------
# DETERMINATION OF SOME GEOMETRIC PROPERTIES
# In the following, some geometric properties of the whole object are
# determined: center, size, etc.
sum_vec = Vector((0.0,0.0,0.0))
# First the center is determined. All coordinates are summed up ...
sum_vec = sum([atom.location for atom in all_atoms], sum_vec)
# ... and the average is taken. This gives the center of the object.
object_center_vec = sum_vec / Number_of_total_atoms
# Now, we determine the size.The farthest atom from the object center is
# taken as a measure. The size is used to place well the camera and light
# into the scene.
object_size_vec = [atom.location - object_center_vec for atom in all_atoms]
object_size = max(object_size_vec).length
# ------------------------------------------------------------------------
# SORTING THE ATOMS
# Lists of atoms of one type are created. Example:
# draw_all_atoms = [ data_hydrogen,data_carbon,data_nitrogen ]
# data_hydrogen = [["Hydrogen", Material_Hydrogen, Vector((x,y,z)), 109], ...]
# Go through the list which contains all types of atoms. It is the list,
# which has been created on the top during reading the PDB file.
# Example: atom_all_types_list = ["hydrogen", "carbon", ...]
draw_all_atoms = []
for atom_type in atom_all_types_list:
# Don't draw 'TER atoms'.
if atom_type[0] == "TER":
continue
# This is the draw list, which contains all atoms of one type (e.g.
# all hydrogens) ...
draw_all_atoms_type = []
# Go through all atoms ...
for atom in all_atoms:
# ... select the atoms of the considered type via comparison ...
if atom.name == atom_type[0]:
# ... and append them to the list 'draw_all_atoms_type'.
draw_all_atoms_type.append([atom.name,
atom.material,
atom.location,
atom.radius])
# Now append the atom list to the list of all types of atoms
draw_all_atoms.append(draw_all_atoms_type)
# ------------------------------------------------------------------------
# COLLECTION
# Before we start to draw the atoms and sticks, we first create a
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
# collection for the molecule. All atoms (balls) and sticks (cylinders)
# are put into this collection.
coll_molecule_name = os.path.basename(filepath_pdb)
scene = bpy.context.scene
coll_molecule = bpy.data.collections.new(coll_molecule_name)
scene.collection.children.link(coll_molecule)
# ------------------------------------------------------------------------
# DRAWING THE ATOMS
bpy.ops.object.select_all(action='DESELECT')
list_coll_elements = []
# For each list of atoms of ONE type (e.g. Hydrogen)
for draw_all_atoms_type in draw_all_atoms:
atom_mesh, coll_element = draw_atoms_one_type(draw_all_atoms_type,
Ball_type,
Ball_azimuth,
Ball_zenith,
Ball_radius_factor,
object_center_vec,
coll_molecule)
atom_object_list.append(atom_mesh)
list_coll_elements.append(coll_element)
# ------------------------------------------------------------------------
# DRAWING THE STICKS: cylinders in a dupliverts structure
if use_sticks == True and use_sticks_type == '0' and all_sticks != []:
sticks = draw_sticks_dupliverts(all_atoms,
atom_all_types_list,
object_center_vec,
all_sticks,
Stick_diameter,
Stick_sectors,
Stick_unit,
Stick_dist,
use_sticks_smooth,
use_sticks_color,
list_coll_elements)
for stick in sticks:
atom_object_list.append(stick)
# ------------------------------------------------------------------------
# DRAWING THE STICKS: skin and subdivision modifier
if use_sticks == True and use_sticks_type == '1' and all_sticks != []:
sticks = draw_sticks_skin(all_atoms,
all_sticks,
Stick_diameter,
use_sticks_smooth,
sticks_subdiv_view,
sticks_subdiv_render,
coll_molecule)
atom_object_list.append(sticks)
# ------------------------------------------------------------------------
# DRAWING THE STICKS: normal cylinders
if use_sticks == True and use_sticks_type == '2' and all_sticks != []:
sticks = draw_sticks_normal(all_atoms,
all_sticks,
object_center_vec,
Stick_diameter,
Stick_sectors,
use_sticks_smooth,
use_sticks_one_object,
use_sticks_one_object_nr,
coll_molecule)
atom_object_list.append(sticks)
# ------------------------------------------------------------------------
# CAMERA and LIGHT SOURCES
camera_light_source(use_camera,
use_light,
object_center_vec,
object_size)
# ------------------------------------------------------------------------
# SELECT ALL LOADED OBJECTS
bpy.ops.object.select_all(action='DESELECT')
obj = None
for obj in atom_object_list:
obj.select_set(True)
# activate the last selected object
if obj:
bpy.context.view_layer.objects.active = obj