Merge branch 'master' into blender2.8
[blender-addons.git] / io_mesh_pdb / import_pdb.py
blob564d26a1eab788e0c9669dcb78cb98bae96bb8df
1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
19 import bpy
20 import bmesh
21 from math import pi, cos, sin, sqrt, ceil
22 from mathutils import Vector, Matrix
23 from copy import copy
25 # -----------------------------------------------------------------------------
26 # Atom, stick and element data
29 # This is a list that contains some data of all possible elements. The structure
30 # is as follows:
32 # 1, "Hydrogen", "H", [0.0,0.0,1.0], 0.32, 0.32, 0.32 , -1 , 1.54 means
34 # No., name, short name, color, radius (used), radius (covalent), radius (atomic),
36 # charge state 1, radius (ionic) 1, charge state 2, radius (ionic) 2, ... all
37 # charge states for any atom are listed, if existing.
38 # The list is fixed and cannot be changed ... (see below)
40 ELEMENTS_DEFAULT = (
41 ( 1, "Hydrogen", "H", ( 1.0, 1.0, 1.0), 0.32, 0.32, 0.79 , -1 , 1.54 ),
42 ( 2, "Helium", "He", ( 0.85, 1.0, 1.0), 0.93, 0.93, 0.49 ),
43 ( 3, "Lithium", "Li", ( 0.8, 0.50, 1.0), 1.23, 1.23, 2.05 , 1 , 0.68 ),
44 ( 4, "Beryllium", "Be", ( 0.76, 1.0, 0.0), 0.90, 0.90, 1.40 , 1 , 0.44 , 2 , 0.35 ),
45 ( 5, "Boron", "B", ( 1.0, 0.70, 0.70), 0.82, 0.82, 1.17 , 1 , 0.35 , 3 , 0.23 ),
46 ( 6, "Carbon", "C", ( 0.56, 0.56, 0.56), 0.77, 0.77, 0.91 , -4 , 2.60 , 4 , 0.16 ),
47 ( 7, "Nitrogen", "N", ( 0.18, 0.31, 0.97), 0.75, 0.75, 0.75 , -3 , 1.71 , 1 , 0.25 , 3 , 0.16 , 5 , 0.13 ),
48 ( 8, "Oxygen", "O", ( 1.0, 0.05, 0.05), 0.73, 0.73, 0.65 , -2 , 1.32 , -1 , 1.76 , 1 , 0.22 , 6 , 0.09 ),
49 ( 9, "Fluorine", "F", ( 0.56, 0.87, 0.31), 0.72, 0.72, 0.57 , -1 , 1.33 , 7 , 0.08 ),
50 (10, "Neon", "Ne", ( 0.70, 0.89, 0.96), 0.71, 0.71, 0.51 , 1 , 1.12 ),
51 (11, "Sodium", "Na", ( 0.67, 0.36, 0.94), 1.54, 1.54, 2.23 , 1 , 0.97 ),
52 (12, "Magnesium", "Mg", ( 0.54, 1.0, 0.0), 1.36, 1.36, 1.72 , 1 , 0.82 , 2 , 0.66 ),
53 (13, "Aluminium", "Al", ( 0.74, 0.65, 0.65), 1.18, 1.18, 1.82 , 3 , 0.51 ),
54 (14, "Silicon", "Si", ( 0.94, 0.78, 0.62), 1.11, 1.11, 1.46 , -4 , 2.71 , -1 , 3.84 , 1 , 0.65 , 4 , 0.42 ),
55 (15, "Phosphorus", "P", ( 1.0, 0.50, 0.0), 1.06, 1.06, 1.23 , -3 , 2.12 , 3 , 0.44 , 5 , 0.35 ),
56 (16, "Sulfur", "S", ( 1.0, 1.0, 0.18), 1.02, 1.02, 1.09 , -2 , 1.84 , 2 , 2.19 , 4 , 0.37 , 6 , 0.30 ),
57 (17, "Chlorine", "Cl", ( 0.12, 0.94, 0.12), 0.99, 0.99, 0.97 , -1 , 1.81 , 5 , 0.34 , 7 , 0.27 ),
58 (18, "Argon", "Ar", ( 0.50, 0.81, 0.89), 0.98, 0.98, 0.88 , 1 , 1.54 ),
59 (19, "Potassium", "K", ( 0.56, 0.25, 0.83), 2.03, 2.03, 2.77 , 1 , 0.81 ),
60 (20, "Calcium", "Ca", ( 0.23, 1.0, 0.0), 1.74, 1.74, 2.23 , 1 , 1.18 , 2 , 0.99 ),
61 (21, "Scandium", "Sc", ( 0.90, 0.90, 0.90), 1.44, 1.44, 2.09 , 3 , 0.73 ),
62 (22, "Titanium", "Ti", ( 0.74, 0.76, 0.78), 1.32, 1.32, 2.00 , 1 , 0.96 , 2 , 0.94 , 3 , 0.76 , 4 , 0.68 ),
63 (23, "Vanadium", "V", ( 0.65, 0.65, 0.67), 1.22, 1.22, 1.92 , 2 , 0.88 , 3 , 0.74 , 4 , 0.63 , 5 , 0.59 ),
64 (24, "Chromium", "Cr", ( 0.54, 0.6, 0.78), 1.18, 1.18, 1.85 , 1 , 0.81 , 2 , 0.89 , 3 , 0.63 , 6 , 0.52 ),
65 (25, "Manganese", "Mn", ( 0.61, 0.47, 0.78), 1.17, 1.17, 1.79 , 2 , 0.80 , 3 , 0.66 , 4 , 0.60 , 7 , 0.46 ),
66 (26, "Iron", "Fe", ( 0.87, 0.4, 0.2), 1.17, 1.17, 1.72 , 2 , 0.74 , 3 , 0.64 ),
67 (27, "Cobalt", "Co", ( 0.94, 0.56, 0.62), 1.16, 1.16, 1.67 , 2 , 0.72 , 3 , 0.63 ),
68 (28, "Nickel", "Ni", ( 0.31, 0.81, 0.31), 1.15, 1.15, 1.62 , 2 , 0.69 ),
69 (29, "Copper", "Cu", ( 0.78, 0.50, 0.2), 1.17, 1.17, 1.57 , 1 , 0.96 , 2 , 0.72 ),
70 (30, "Zinc", "Zn", ( 0.49, 0.50, 0.69), 1.25, 1.25, 1.53 , 1 , 0.88 , 2 , 0.74 ),
71 (31, "Gallium", "Ga", ( 0.76, 0.56, 0.56), 1.26, 1.26, 1.81 , 1 , 0.81 , 3 , 0.62 ),
72 (32, "Germanium", "Ge", ( 0.4, 0.56, 0.56), 1.22, 1.22, 1.52 , -4 , 2.72 , 2 , 0.73 , 4 , 0.53 ),
73 (33, "Arsenic", "As", ( 0.74, 0.50, 0.89), 1.20, 1.20, 1.33 , -3 , 2.22 , 3 , 0.58 , 5 , 0.46 ),
74 (34, "Selenium", "Se", ( 1.0, 0.63, 0.0), 1.16, 1.16, 1.22 , -2 , 1.91 , -1 , 2.32 , 1 , 0.66 , 4 , 0.50 , 6 , 0.42 ),
75 (35, "Bromine", "Br", ( 0.65, 0.16, 0.16), 1.14, 1.14, 1.12 , -1 , 1.96 , 5 , 0.47 , 7 , 0.39 ),
76 (36, "Krypton", "Kr", ( 0.36, 0.72, 0.81), 1.31, 1.31, 1.24 ),
77 (37, "Rubidium", "Rb", ( 0.43, 0.18, 0.69), 2.16, 2.16, 2.98 , 1 , 1.47 ),
78 (38, "Strontium", "Sr", ( 0.0, 1.0, 0.0), 1.91, 1.91, 2.45 , 2 , 1.12 ),
79 (39, "Yttrium", "Y", ( 0.58, 1.0, 1.0), 1.62, 1.62, 2.27 , 3 , 0.89 ),
80 (40, "Zirconium", "Zr", ( 0.58, 0.87, 0.87), 1.45, 1.45, 2.16 , 1 , 1.09 , 4 , 0.79 ),
81 (41, "Niobium", "Nb", ( 0.45, 0.76, 0.78), 1.34, 1.34, 2.08 , 1 , 1.00 , 4 , 0.74 , 5 , 0.69 ),
82 (42, "Molybdenum", "Mo", ( 0.32, 0.70, 0.70), 1.30, 1.30, 2.01 , 1 , 0.93 , 4 , 0.70 , 6 , 0.62 ),
83 (43, "Technetium", "Tc", ( 0.23, 0.61, 0.61), 1.27, 1.27, 1.95 , 7 , 0.97 ),
84 (44, "Ruthenium", "Ru", ( 0.14, 0.56, 0.56), 1.25, 1.25, 1.89 , 4 , 0.67 ),
85 (45, "Rhodium", "Rh", ( 0.03, 0.49, 0.54), 1.25, 1.25, 1.83 , 3 , 0.68 ),
86 (46, "Palladium", "Pd", ( 0.0, 0.41, 0.52), 1.28, 1.28, 1.79 , 2 , 0.80 , 4 , 0.65 ),
87 (47, "Silver", "Ag", ( 0.75, 0.75, 0.75), 1.34, 1.34, 1.75 , 1 , 1.26 , 2 , 0.89 ),
88 (48, "Cadmium", "Cd", ( 1.0, 0.85, 0.56), 1.48, 1.48, 1.71 , 1 , 1.14 , 2 , 0.97 ),
89 (49, "Indium", "In", ( 0.65, 0.45, 0.45), 1.44, 1.44, 2.00 , 3 , 0.81 ),
90 (50, "Tin", "Sn", ( 0.4, 0.50, 0.50), 1.41, 1.41, 1.72 , -4 , 2.94 , -1 , 3.70 , 2 , 0.93 , 4 , 0.71 ),
91 (51, "Antimony", "Sb", ( 0.61, 0.38, 0.70), 1.40, 1.40, 1.53 , -3 , 2.45 , 3 , 0.76 , 5 , 0.62 ),
92 (52, "Tellurium", "Te", ( 0.83, 0.47, 0.0), 1.36, 1.36, 1.42 , -2 , 2.11 , -1 , 2.50 , 1 , 0.82 , 4 , 0.70 , 6 , 0.56 ),
93 (53, "Iodine", "I", ( 0.58, 0.0, 0.58), 1.33, 1.33, 1.32 , -1 , 2.20 , 5 , 0.62 , 7 , 0.50 ),
94 (54, "Xenon", "Xe", ( 0.25, 0.61, 0.69), 1.31, 1.31, 1.24 ),
95 (55, "Caesium", "Cs", ( 0.34, 0.09, 0.56), 2.35, 2.35, 3.35 , 1 , 1.67 ),
96 (56, "Barium", "Ba", ( 0.0, 0.78, 0.0), 1.98, 1.98, 2.78 , 1 , 1.53 , 2 , 1.34 ),
97 (57, "Lanthanum", "La", ( 0.43, 0.83, 1.0), 1.69, 1.69, 2.74 , 1 , 1.39 , 3 , 1.06 ),
98 (58, "Cerium", "Ce", ( 1.0, 1.0, 0.78), 1.65, 1.65, 2.70 , 1 , 1.27 , 3 , 1.03 , 4 , 0.92 ),
99 (59, "Praseodymium", "Pr", ( 0.85, 1.0, 0.78), 1.65, 1.65, 2.67 , 3 , 1.01 , 4 , 0.90 ),
100 (60, "Neodymium", "Nd", ( 0.78, 1.0, 0.78), 1.64, 1.64, 2.64 , 3 , 0.99 ),
101 (61, "Promethium", "Pm", ( 0.63, 1.0, 0.78), 1.63, 1.63, 2.62 , 3 , 0.97 ),
102 (62, "Samarium", "Sm", ( 0.56, 1.0, 0.78), 1.62, 1.62, 2.59 , 3 , 0.96 ),
103 (63, "Europium", "Eu", ( 0.38, 1.0, 0.78), 1.85, 1.85, 2.56 , 2 , 1.09 , 3 , 0.95 ),
104 (64, "Gadolinium", "Gd", ( 0.27, 1.0, 0.78), 1.61, 1.61, 2.54 , 3 , 0.93 ),
105 (65, "Terbium", "Tb", ( 0.18, 1.0, 0.78), 1.59, 1.59, 2.51 , 3 , 0.92 , 4 , 0.84 ),
106 (66, "Dysprosium", "Dy", ( 0.12, 1.0, 0.78), 1.59, 1.59, 2.49 , 3 , 0.90 ),
107 (67, "Holmium", "Ho", ( 0.0, 1.0, 0.61), 1.58, 1.58, 2.47 , 3 , 0.89 ),
108 (68, "Erbium", "Er", ( 0.0, 0.90, 0.45), 1.57, 1.57, 2.45 , 3 , 0.88 ),
109 (69, "Thulium", "Tm", ( 0.0, 0.83, 0.32), 1.56, 1.56, 2.42 , 3 , 0.87 ),
110 (70, "Ytterbium", "Yb", ( 0.0, 0.74, 0.21), 1.74, 1.74, 2.40 , 2 , 0.93 , 3 , 0.85 ),
111 (71, "Lutetium", "Lu", ( 0.0, 0.67, 0.14), 1.56, 1.56, 2.25 , 3 , 0.85 ),
112 (72, "Hafnium", "Hf", ( 0.30, 0.76, 1.0), 1.44, 1.44, 2.16 , 4 , 0.78 ),
113 (73, "Tantalum", "Ta", ( 0.30, 0.65, 1.0), 1.34, 1.34, 2.09 , 5 , 0.68 ),
114 (74, "Tungsten", "W", ( 0.12, 0.58, 0.83), 1.30, 1.30, 2.02 , 4 , 0.70 , 6 , 0.62 ),
115 (75, "Rhenium", "Re", ( 0.14, 0.49, 0.67), 1.28, 1.28, 1.97 , 4 , 0.72 , 7 , 0.56 ),
116 (76, "Osmium", "Os", ( 0.14, 0.4, 0.58), 1.26, 1.26, 1.92 , 4 , 0.88 , 6 , 0.69 ),
117 (77, "Iridium", "Ir", ( 0.09, 0.32, 0.52), 1.27, 1.27, 1.87 , 4 , 0.68 ),
118 (78, "Platinium", "Pt", ( 0.81, 0.81, 0.87), 1.30, 1.30, 1.83 , 2 , 0.80 , 4 , 0.65 ),
119 (79, "Gold", "Au", ( 1.0, 0.81, 0.13), 1.34, 1.34, 1.79 , 1 , 1.37 , 3 , 0.85 ),
120 (80, "Mercury", "Hg", ( 0.72, 0.72, 0.81), 1.49, 1.49, 1.76 , 1 , 1.27 , 2 , 1.10 ),
121 (81, "Thallium", "Tl", ( 0.65, 0.32, 0.30), 1.48, 1.48, 2.08 , 1 , 1.47 , 3 , 0.95 ),
122 (82, "Lead", "Pb", ( 0.34, 0.34, 0.38), 1.47, 1.47, 1.81 , 2 , 1.20 , 4 , 0.84 ),
123 (83, "Bismuth", "Bi", ( 0.61, 0.30, 0.70), 1.46, 1.46, 1.63 , 1 , 0.98 , 3 , 0.96 , 5 , 0.74 ),
124 (84, "Polonium", "Po", ( 0.67, 0.36, 0.0), 1.46, 1.46, 1.53 , 6 , 0.67 ),
125 (85, "Astatine", "At", ( 0.45, 0.30, 0.27), 1.45, 1.45, 1.43 , -3 , 2.22 , 3 , 0.85 , 5 , 0.46 ),
126 (86, "Radon", "Rn", ( 0.25, 0.50, 0.58), 1.00, 1.00, 1.34 ),
127 (87, "Francium", "Fr", ( 0.25, 0.0, 0.4), 1.00, 1.00, 1.00 , 1 , 1.80 ),
128 (88, "Radium", "Ra", ( 0.0, 0.49, 0.0), 1.00, 1.00, 1.00 , 2 , 1.43 ),
129 (89, "Actinium", "Ac", ( 0.43, 0.67, 0.98), 1.00, 1.00, 1.00 , 3 , 1.18 ),
130 (90, "Thorium", "Th", ( 0.0, 0.72, 1.0), 1.65, 1.65, 1.00 , 4 , 1.02 ),
131 (91, "Protactinium", "Pa", ( 0.0, 0.63, 1.0), 1.00, 1.00, 1.00 , 3 , 1.13 , 4 , 0.98 , 5 , 0.89 ),
132 (92, "Uranium", "U", ( 0.0, 0.56, 1.0), 1.42, 1.42, 1.00 , 4 , 0.97 , 6 , 0.80 ),
133 (93, "Neptunium", "Np", ( 0.0, 0.50, 1.0), 1.00, 1.00, 1.00 , 3 , 1.10 , 4 , 0.95 , 7 , 0.71 ),
134 (94, "Plutonium", "Pu", ( 0.0, 0.41, 1.0), 1.00, 1.00, 1.00 , 3 , 1.08 , 4 , 0.93 ),
135 (95, "Americium", "Am", ( 0.32, 0.36, 0.94), 1.00, 1.00, 1.00 , 3 , 1.07 , 4 , 0.92 ),
136 (96, "Curium", "Cm", ( 0.47, 0.36, 0.89), 1.00, 1.00, 1.00 ),
137 (97, "Berkelium", "Bk", ( 0.54, 0.30, 0.89), 1.00, 1.00, 1.00 ),
138 (98, "Californium", "Cf", ( 0.63, 0.21, 0.83), 1.00, 1.00, 1.00 ),
139 (99, "Einsteinium", "Es", ( 0.70, 0.12, 0.83), 1.00, 1.00, 1.00 ),
140 (100, "Fermium", "Fm", ( 0.70, 0.12, 0.72), 1.00, 1.00, 1.00 ),
141 (101, "Mendelevium", "Md", ( 0.70, 0.05, 0.65), 1.00, 1.00, 1.00 ),
142 (102, "Nobelium", "No", ( 0.74, 0.05, 0.52), 1.00, 1.00, 1.00 ),
143 (103, "Lawrencium", "Lr", ( 0.78, 0.0, 0.4), 1.00, 1.00, 1.00 ),
144 (104, "Vacancy", "Vac", ( 0.5, 0.5, 0.5), 1.00, 1.00, 1.00),
145 (105, "Default", "Default", ( 1.0, 1.0, 1.0), 1.00, 1.00, 1.00),
146 (106, "Stick", "Stick", ( 0.5, 0.5, 0.5), 1.00, 1.00, 1.00),
149 # This list here contains all data of the elements and will be used during
150 # runtime. It is a list of classes.
151 # During executing Atomic Blender, the list will be initialized with the fixed
152 # data from above via the class structure below (ElementProp). We
153 # have then one fixed list (above), which will never be changed, and a list of
154 # classes with same data. The latter can be modified via loading a separate
155 # custom data file.
156 ELEMENTS = []
158 # This is the class, which stores the properties for one element.
159 class ElementProp(object):
160 __slots__ = ('number', 'name', 'short_name', 'color', 'radii', 'radii_ionic')
161 def __init__(self, number, name, short_name, color, radii, radii_ionic):
162 self.number = number
163 self.name = name
164 self.short_name = short_name
165 self.color = color
166 self.radii = radii
167 self.radii_ionic = radii_ionic
169 # This is the class, which stores the properties of one atom.
170 class AtomProp(object):
171 __slots__ = ('element', 'name', 'location', 'radius', 'color', 'material')
172 def __init__(self, element, name, location, radius, color, material):
173 self.element = element
174 self.name = name
175 self.location = location
176 self.radius = radius
177 self.color = color
178 self.material = material
180 # This is the class, which stores the two atoms of one stick.
181 class StickProp(object):
182 __slots__ = ('atom1', 'atom2', 'number', 'dist')
183 def __init__(self, atom1, atom2, number, dist):
184 self.atom1 = atom1
185 self.atom2 = atom2
186 self.number = number
187 self.dist = dist
189 # -----------------------------------------------------------------------------
190 # Some basic routines
193 # The function, which reads all necessary properties of the elements.
194 def read_elements():
196 del ELEMENTS[:]
198 for item in ELEMENTS_DEFAULT:
200 # All three radii into a list
201 radii = [item[4],item[5],item[6]]
202 # The handling of the ionic radii will be done later. So far, it is an
203 # empty list.
204 radii_ionic = []
206 li = ElementProp(item[0],item[1],item[2],item[3],
207 radii,radii_ionic)
208 ELEMENTS.append(li)
211 # The function, which reads the x,y,z positions of all atoms in a PDB
212 # file.
214 # filepath_pdb: path to pdb file
215 # radiustype : '0' default
216 # '1' atomic radii
217 # '2' van der Waals
218 def read_pdb_file(filepath_pdb, radiustype):
220 # The list of all atoms as read from the PDB file.
221 all_atoms = []
223 # Open the pdb file ...
224 filepath_pdb_p = open(filepath_pdb, "r")
226 #Go to the line, in which "ATOM" or "HETATM" appears.
227 for line in filepath_pdb_p:
228 split_list = line.split(' ')
229 if "ATOM" in split_list[0]:
230 break
231 if "HETATM" in split_list[0]:
232 break
234 j = 0
235 # This is in fact an endless 'while loop', ...
236 while j > -1:
238 # ... the loop is broken here (EOF) ...
239 if line == "":
240 break
242 # If there is a "TER" we need to put empty entries into the lists
243 # in order to not destroy the order of atom numbers and same numbers
244 # used for sticks. "TER? What is that?" TER indicates the end of a
245 # list of ATOM/HETATM records for a chain.
246 if "TER" in line:
247 short_name = "TER"
248 name = "TER"
249 radius = 0.0
250 color = [0,0,0]
251 location = Vector((0,0,0))
252 # Append the TER into the list. Material remains empty so far.
253 all_atoms.append(AtomProp(short_name,
254 name,
255 location,
256 radius,
257 color,[]))
259 # If 'ATOM or 'HETATM' appears in the line then do ...
260 elif "ATOM" in line or "HETATM" in line:
262 # What follows is due to deviations which appear from PDB to
263 # PDB file. It is very special!
265 # PLEASE, DO NOT CHANGE! ............................... from here
266 if line[12:13] == " " or line[12:13].isdigit() == True:
267 short_name = line[13:14]
268 if line[14:15].islower() == True:
269 short_name = short_name + line[14:15]
270 elif line[12:13].isupper() == True:
271 short_name = line[12:13]
272 if line[13:14].isalpha() == True:
273 short_name = short_name + line[13:14]
274 else:
275 print("Atomic Blender: Strange error in PDB file.\n"
276 "Look for element names at positions 13-16 and 78-79.\n")
277 return -1
279 if len(line) >= 78:
281 if line[76:77] == " ":
282 short_name2 = line[76:77]
283 else:
284 short_name2 = line[76:78]
286 if short_name2.isalpha() == True:
287 FOUND = False
288 for element in ELEMENTS:
289 if str.upper(short_name2) == str.upper(element.short_name):
290 FOUND = True
291 break
292 if FOUND == False:
293 short_name = short_name2
294 # ....................................................... to here.
296 # Go through all elements and find the element of the current atom.
297 FLAG_FOUND = False
298 for element in ELEMENTS:
299 if str.upper(short_name) == str.upper(element.short_name):
300 # Give the atom its proper names, color and radius:
301 short_name = str.upper(element.short_name)
302 name = element.name
303 # int(radiustype) => type of radius:
304 # pre-defined (0), atomic (1) or van der Waals (2)
305 radius = float(element.radii[int(radiustype)])
306 color = element.color
307 FLAG_FOUND = True
308 break
310 # Is it a vacancy or an 'unknown atom' ?
311 if FLAG_FOUND == False:
312 # Give this atom also a name. If it is an 'X' then it is a
313 # vacancy. Otherwise ...
314 if "X" in short_name:
315 short_name = "VAC"
316 name = "Vacancy"
317 radius = float(ELEMENTS[-3].radii[int(radiustype)])
318 color = ELEMENTS[-3].color
319 # ... take what is written in the PDB file. These are somewhat
320 # unknown atoms. This should never happen, the element list is
321 # almost complete. However, we do this due to security reasons.
322 else:
323 short_name = str.upper(short_name)
324 name = str.upper(short_name)
325 radius = float(ELEMENTS[-2].radii[int(radiustype)])
326 color = ELEMENTS[-2].color
328 # x,y and z are at fixed positions in the PDB file.
329 x = float(line[30:38].rsplit()[0])
330 y = float(line[38:46].rsplit()[0])
331 z = float(line[46:55].rsplit()[0])
333 location = Vector((x,y,z))
335 j += 1
337 # Append the atom to the list. Material remains empty so far.
338 all_atoms.append(AtomProp(short_name,
339 name,
340 location,
341 radius,
342 color,[]))
344 line = filepath_pdb_p.readline()
345 line = line[:-1]
347 filepath_pdb_p.close()
348 # From above it can be clearly seen that j is now the number of all atoms.
349 Number_of_total_atoms = j
351 return (Number_of_total_atoms, all_atoms)
354 # The function, which reads the sticks in a PDB file.
355 def read_pdb_file_sticks(filepath_pdb, use_sticks_bonds, all_atoms):
357 # The list of all sticks.
358 all_sticks = []
360 # Open the PDB file.
361 filepath_pdb_p = open(filepath_pdb, "r")
363 line = filepath_pdb_p.readline()
364 split_list = line.split(' ')
366 # Go to the first entry
367 if "CONECT" not in split_list[0]:
368 for line in filepath_pdb_p:
369 split_list = line.split(' ')
370 if "CONECT" in split_list[0]:
371 break
373 Number_of_sticks = 0
374 sticks_double = 0
375 j = 0
376 # This is in fact an endless while loop, ...
377 while j > -1:
379 # ... which is broken here (EOF) ...
380 if line == "":
381 break
382 # ... or here, when no 'CONECT' appears anymore.
383 if "CONECT" not in line:
384 break
386 # The strings of the atom numbers do have a clear position in the file
387 # (From 7 to 12, from 13 to 18 and so on.) and one needs to consider
388 # this. One could also use the split function but then one gets into
389 # trouble if there are lots of atoms: For instance, it may happen that
390 # one has
391 # CONECT 11111 22244444
393 # In Fact it means that atom No. 11111 has a connection with atom
394 # No. 222 but also with atom No. 44444. The split function would give
395 # me only two numbers (11111 and 22244444), which is wrong.
397 # Cut spaces from the right and 'CONECT' at the beginning
398 line = line.rstrip()
399 line = line[6:]
400 # Amount of loops
401 length = len(line)
402 loops = int(length/5)
404 # List of atoms
405 atom_list = []
406 for i in range(loops):
407 number = line[5*i:5*(i+1)].rsplit()
408 if number != []:
409 if number[0].isdigit() == True:
410 atom_number = int(number[0])
411 atom_list.append(atom_number)
413 # The first atom is connected with all the others in the list.
414 atom1 = atom_list[0]
416 # For all the other atoms in the list do:
417 for atom2 in atom_list[1:]:
419 if use_sticks_bonds == True:
420 number = atom_list[1:].count(atom2)
422 if number == 2 or number == 3:
423 basis_list = list(set(atom_list[1:]))
425 if len(basis_list) > 1:
426 basis1 = (all_atoms[atom1-1].location
427 - all_atoms[basis_list[0]-1].location)
428 basis2 = (all_atoms[atom1-1].location
429 - all_atoms[basis_list[1]-1].location)
430 plane_n = basis1.cross(basis2)
432 dist_n = (all_atoms[atom1-1].location
433 - all_atoms[atom2-1].location)
434 dist_n = dist_n.cross(plane_n)
435 dist_n = dist_n / dist_n.length
436 else:
437 dist_n = (all_atoms[atom1-1].location
438 - all_atoms[atom2-1].location)
439 dist_n = Vector((dist_n[1],-dist_n[0],0))
440 dist_n = dist_n / dist_n.length
441 elif number > 3:
442 number = 1
443 dist_n = None
444 else:
445 dist_n = None
446 else:
447 number = 1
448 dist_n = None
450 # Note that in a PDB file, sticks of one atom pair can appear a
451 # couple of times. (Only god knows why ...)
452 # So, does a stick between the considered atoms already exist?
453 FLAG_BAR = False
454 for k in range(Number_of_sticks):
455 if ((all_sticks[k].atom1 == atom1 and all_sticks[k].atom2 == atom2) or
456 (all_sticks[k].atom2 == atom1 and all_sticks[k].atom1 == atom2)):
457 sticks_double += 1
458 # If yes, then FLAG on 'True'.
459 FLAG_BAR = True
460 break
462 # If the stick is not yet registered (FLAG_BAR == False), then
463 # register it!
464 if FLAG_BAR == False:
465 all_sticks.append(StickProp(atom1,atom2,number,dist_n))
466 Number_of_sticks += 1
467 j += 1
469 line = filepath_pdb_p.readline()
470 line = line.rstrip()
472 filepath_pdb_p.close()
474 return all_sticks
477 # Function, which produces a cylinder. All is somewhat easy to undertsand.
478 def build_stick(radius, length, sectors):
480 dphi = 2.0 * pi/(float(sectors)-1)
482 # Vertices
483 vertices_top = [Vector((0,0,length / 2.0))]
484 vertices_bottom = [Vector((0,0,-length / 2.0))]
485 vertices = []
486 for i in range(sectors-1):
487 x = radius * cos( dphi * i )
488 y = radius * sin( dphi * i )
489 z = length / 2.0
490 vertex = Vector((x,y,z))
491 vertices_top.append(vertex)
492 z = -length / 2.0
493 vertex = Vector((x,y,z))
494 vertices_bottom.append(vertex)
495 vertices = vertices_top + vertices_bottom
497 # Side facets (Cylinder)
498 faces1 = []
499 for i in range(sectors-1):
500 if i == sectors-2:
501 faces1.append( [i+1, 1, 1+sectors, i+1+sectors] )
502 else:
503 faces1.append( [i+1, i+2, i+2+sectors, i+1+sectors] )
505 # Top facets
506 faces2 = []
507 for i in range(sectors-1):
508 if i == sectors-2:
509 face_top = [0,sectors-1,1]
510 face_bottom = [sectors,2*sectors-1,sectors+1]
511 else:
512 face_top = [0]
513 face_bottom = [sectors]
514 for j in range(2):
515 face_top.append(i+j+1)
516 face_bottom.append(i+j+1+sectors)
517 faces2.append(face_top)
518 faces2.append(face_bottom)
520 # Build the mesh, Cylinder
521 cylinder = bpy.data.meshes.new("Sticks_Cylinder")
522 cylinder.from_pydata(vertices, [], faces1)
523 cylinder.update()
524 new_cylinder = bpy.data.objects.new("Sticks_Cylinder", cylinder)
525 bpy.context.scene.objects.link(new_cylinder)
527 # Build the mesh, Cups
528 cups = bpy.data.meshes.new("Sticks_Cups")
529 cups.from_pydata(vertices, [], faces2)
530 cups.update()
531 new_cups = bpy.data.objects.new("Sticks_Cups", cups)
532 bpy.context.scene.objects.link(new_cups)
534 return (new_cylinder, new_cups)
537 # Function, which puts a camera and light source into the 3D scene
538 def camera_light_source(use_camera,
539 use_light,
540 object_center_vec,
541 object_size):
543 camera_factor = 15.0
545 # If chosen a camera is put into the scene.
546 if use_camera == True:
548 # Assume that the object is put into the global origin. Then, the
549 # camera is moved in x and z direction, not in y. The object has its
550 # size at distance sqrt(object_size) from the origin. So, move the
551 # camera by this distance times a factor of camera_factor in x and z.
552 # Then add x, y and z of the origin of the object.
553 object_camera_vec = Vector((sqrt(object_size) * camera_factor,
554 0.0,
555 sqrt(object_size) * camera_factor))
556 camera_xyz_vec = object_center_vec + object_camera_vec
558 # Create the camera
559 current_layers=bpy.context.scene.layers
560 camera_data = bpy.data.cameras.new("A_camera")
561 camera_data.lens = 45
562 camera_data.clip_end = 500.0
563 camera = bpy.data.objects.new("A_camera", camera_data)
564 camera.location = camera_xyz_vec
565 camera.layers = current_layers
566 bpy.context.scene.objects.link(camera)
568 # Here the camera is rotated such it looks towards the center of
569 # the object. The [0.0, 0.0, 1.0] vector along the z axis
570 z_axis_vec = Vector((0.0, 0.0, 1.0))
571 # The angle between the last two vectors
572 angle = object_camera_vec.angle(z_axis_vec, 0)
573 # The cross-product of z_axis_vec and object_camera_vec
574 axis_vec = z_axis_vec.cross(object_camera_vec)
575 # Rotate 'axis_vec' by 'angle' and convert this to euler parameters.
576 # 4 is the size of the matrix.
577 camera.rotation_euler = Matrix.Rotation(angle, 4, axis_vec).to_euler()
579 # Rotate the camera around its axis by 90° such that we have a nice
580 # camera position and view onto the object.
581 bpy.ops.object.select_all(action='DESELECT')
582 camera.select = True
583 bpy.ops.transform.rotate(value=(90.0*2*pi/360.0),
584 axis=object_camera_vec,
585 constraint_axis=(False, False, False),
586 constraint_orientation='GLOBAL',
587 mirror=False, proportional='DISABLED',
588 proportional_edit_falloff='SMOOTH',
589 proportional_size=1, snap=False,
590 snap_target='CLOSEST', snap_point=(0, 0, 0),
591 snap_align=False, snap_normal=(0, 0, 0),
592 release_confirm=False)
594 # Here a lamp is put into the scene, if chosen.
595 if use_light == True:
597 # This is the distance from the object measured in terms of %
598 # of the camera distance. It is set onto 50% (1/2) distance.
599 light_dl = sqrt(object_size) * 15 * 0.5
600 # This is a factor to which extend the lamp shall go to the right
601 # (from the camera point of view).
602 light_dy_right = light_dl * (3.0/4.0)
604 # Create x, y and z for the lamp.
605 object_light_vec = Vector((light_dl,light_dy_right,light_dl))
606 light_xyz_vec = object_center_vec + object_light_vec
608 # Create the lamp
609 current_layers=bpy.context.scene.layers
610 light_data = bpy.data.lights.new(name="A_light", type="POINT")
611 light_data.distance = 500.0
612 light_data.energy = 3.0
613 light_data.shadow_method = 'RAY_SHADOW'
614 lamp = bpy.data.objects.new("A_light", light_data)
615 lamp.location = light_xyz_vec
616 lamp.layers = current_layers
617 bpy.context.scene.objects.link(lamp)
619 # Some settings for the World: a bit ambient occlusion
620 bpy.context.scene.world.light_settings.use_ambient_occlusion = True
621 bpy.context.scene.world.light_settings.ao_factor = 0.2
624 # Function, which draws the atoms of one type (balls). This is one
625 # dupliverts structure then.
626 # Return: the dupliverts structure
627 def draw_atoms_one_type(draw_all_atoms_type,
628 Ball_type,
629 Ball_azimuth,
630 Ball_zenith,
631 Ball_radius_factor,
632 object_center_vec):
634 # Create first the vertices composed of the coordinates of all
635 # atoms of one type
636 atom_vertices = []
637 for atom in draw_all_atoms_type:
638 # In fact, the object is created in the World's origin.
639 # This is why 'object_center_vec' is subtracted. At the end
640 # the whole object is translated back to 'object_center_vec'.
641 atom_vertices.append(atom[2] - object_center_vec)
643 # Build the mesh
644 atom_mesh = bpy.data.meshes.new("Mesh_"+atom[0])
645 atom_mesh.from_pydata(atom_vertices, [], [])
646 atom_mesh.update()
647 new_atom_mesh = bpy.data.objects.new(atom[0], atom_mesh)
648 bpy.context.scene.objects.link(new_atom_mesh)
650 # Now, build a representative sphere (atom).
651 current_layers = bpy.context.scene.layers
653 if atom[0] == "Vacancy":
654 bpy.ops.mesh.primitive_cube_add(
655 view_align=False, enter_editmode=False,
656 location=(0.0, 0.0, 0.0),
657 rotation=(0.0, 0.0, 0.0),
658 layers=current_layers)
659 else:
660 # NURBS balls
661 if Ball_type == "0":
662 bpy.ops.surface.primitive_nurbs_surface_sphere_add(
663 view_align=False, enter_editmode=False,
664 location=(0,0,0), rotation=(0.0, 0.0, 0.0),
665 layers=current_layers)
666 # UV balls
667 elif Ball_type == "1":
668 bpy.ops.mesh.primitive_uv_sphere_add(
669 segments=Ball_azimuth, ring_count=Ball_zenith,
670 size=1, view_align=False, enter_editmode=False,
671 location=(0,0,0), rotation=(0, 0, 0),
672 layers=current_layers)
673 # Meta balls
674 elif Ball_type == "2":
675 bpy.ops.object.metaball_add(type='BALL', view_align=False,
676 enter_editmode=False, location=(0, 0, 0),
677 rotation=(0, 0, 0), layers=current_layers)
679 ball = bpy.context.scene.objects.active
680 ball.scale = (atom[3]*Ball_radius_factor,) * 3
682 if atom[0] == "Vacancy":
683 ball.name = "Cube_"+atom[0]
684 else:
685 ball.name = "Ball_"+atom[0]
686 ball.active_material = atom[1]
687 ball.parent = new_atom_mesh
688 new_atom_mesh.dupli_type = 'VERTS'
689 # The object is back translated to 'object_center_vec'.
690 new_atom_mesh.location = object_center_vec
692 return new_atom_mesh
695 # Function, which draws the sticks with help of the dupliverts technique.
696 # Return: list of dupliverts structures.
697 def draw_sticks_dupliverts(all_atoms,
698 atom_all_types_list,
699 center,
700 all_sticks,
701 Stick_diameter,
702 Stick_sectors,
703 Stick_unit,
704 Stick_dist,
705 use_sticks_smooth,
706 use_sticks_color):
708 dl = Stick_unit
710 if use_sticks_color == False:
711 bpy.ops.object.material_slot_add()
712 stick_material = bpy.data.materials.new(ELEMENTS[-1].name)
713 stick_material.diffuse_color = ELEMENTS[-1].color
715 # Sort the sticks and put them into a new list such that ...
716 sticks_all_lists = []
717 if use_sticks_color == True:
718 for atom_type in atom_all_types_list:
719 if atom_type[0] == "TER":
720 continue
721 sticks_list = []
722 for stick in all_sticks:
724 for repeat in range(stick.number):
726 atom1 = copy(all_atoms[stick.atom1-1].location)-center
727 atom2 = copy(all_atoms[stick.atom2-1].location)-center
729 dist = Stick_diameter * Stick_dist
731 if stick.number == 2:
732 if repeat == 0:
733 atom1 += (stick.dist * dist)
734 atom2 += (stick.dist * dist)
735 if repeat == 1:
736 atom1 -= (stick.dist * dist)
737 atom2 -= (stick.dist * dist)
739 if stick.number == 3:
740 if repeat == 0:
741 atom1 += (stick.dist * dist)
742 atom2 += (stick.dist * dist)
743 if repeat == 2:
744 atom1 -= (stick.dist * dist)
745 atom2 -= (stick.dist * dist)
747 dv = atom1 - atom2
748 n = dv / dv.length
749 if atom_type[0] == all_atoms[stick.atom1-1].name:
750 location = atom1
751 name = "_" + all_atoms[stick.atom1-1].name
752 material = all_atoms[stick.atom1-1].material
753 sticks_list.append([name, location, dv, material])
754 if atom_type[0] == all_atoms[stick.atom2-1].name:
755 location = atom1 - n * dl * int(ceil(dv.length / (2.0 * dl)))
756 name = "_" + all_atoms[stick.atom2-1].name
757 material = all_atoms[stick.atom2-1].material
758 sticks_list.append([name, location, dv, material])
760 if sticks_list != []:
761 sticks_all_lists.append(sticks_list)
762 else:
763 sticks_list = []
764 for stick in all_sticks:
766 if stick.number > 3:
767 stick.number = 1
769 for repeat in range(stick.number):
771 atom1 = copy(all_atoms[stick.atom1-1].location)-center
772 atom2 = copy(all_atoms[stick.atom2-1].location)-center
774 dist = Stick_diameter * Stick_dist
776 if stick.number == 2:
777 if repeat == 0:
778 atom1 += (stick.dist * dist)
779 atom2 += (stick.dist * dist)
780 if repeat == 1:
781 atom1 -= (stick.dist * dist)
782 atom2 -= (stick.dist * dist)
783 if stick.number == 3:
784 if repeat == 0:
785 atom1 += (stick.dist * dist)
786 atom2 += (stick.dist * dist)
787 if repeat == 2:
788 atom1 -= (stick.dist * dist)
789 atom2 -= (stick.dist * dist)
791 dv = atom1 - atom2
792 n = dv / dv.length
793 location = atom1
794 material = stick_material
795 sticks_list.append(["", location, dv, material])
797 sticks_all_lists.append(sticks_list)
799 atom_object_list = []
800 # ... the sticks in the list can be drawn:
801 for stick_list in sticks_all_lists:
802 vertices = []
803 faces = []
804 i = 0
806 # What follows is school mathematics! :-)
807 for stick in stick_list:
809 dv = stick[2]
810 v1 = stick[1]
811 n = dv / dv.length
812 gamma = -n * v1
813 b = v1 + gamma * n
814 n_b = b / b.length
816 if use_sticks_color == True:
817 loops = int(ceil(dv.length / (2.0 * dl)))
818 else:
819 loops = int(ceil(dv.length / dl))
821 for j in range(loops):
823 g = v1 - n * dl / 2.0 - n * dl * j
824 p1 = g + n_b * Stick_diameter
825 p2 = g - n_b * Stick_diameter
826 p3 = g - n_b.cross(n) * Stick_diameter
827 p4 = g + n_b.cross(n) * Stick_diameter
829 vertices.append(p1)
830 vertices.append(p2)
831 vertices.append(p3)
832 vertices.append(p4)
833 faces.append((i*4+0,i*4+2,i*4+1,i*4+3))
834 i += 1
836 # Build the mesh.
837 mesh = bpy.data.meshes.new("Sticks"+stick[0])
838 mesh.from_pydata(vertices, [], faces)
839 mesh.update()
840 new_mesh = bpy.data.objects.new("Sticks"+stick[0], mesh)
841 bpy.context.scene.objects.link(new_mesh)
843 # Build the object.
844 # Get the cylinder from the 'build_stick' function.
845 object_stick = build_stick(Stick_diameter, dl, Stick_sectors)
846 stick_cylinder = object_stick[0]
847 stick_cylinder.active_material = stick[3]
848 stick_cups = object_stick[1]
849 stick_cups.active_material = stick[3]
851 # Smooth the cylinders.
852 if use_sticks_smooth == True:
853 bpy.ops.object.select_all(action='DESELECT')
854 stick_cylinder.select = True
855 stick_cups.select = True
856 bpy.ops.object.shade_smooth()
858 # Parenting the mesh to the cylinder.
859 stick_cylinder.parent = new_mesh
860 stick_cups.parent = new_mesh
861 new_mesh.dupli_type = 'FACES'
862 new_mesh.location = center
863 atom_object_list.append(new_mesh)
865 # Return the list of dupliverts structures.
866 return atom_object_list
869 # Function, which draws the sticks with help of the skin and subdivision
870 # modifiers.
871 def draw_sticks_skin(all_atoms,
872 all_sticks,
873 Stick_diameter,
874 use_sticks_smooth,
875 sticks_subdiv_view,
876 sticks_subdiv_render):
878 # These counters are for the edges, in the shape [i,i+1].
879 i = 0
881 # This is the list of vertices, containing the atom position
882 # (vectors)).
883 stick_vertices = []
884 # This is the 'same' list, which contains not vector position of
885 # the atoms but their numbers. It is used to handle the edges.
886 stick_vertices_nr = []
887 # This is the list of edges.
888 stick_edges = []
890 # Go through the list of all sticks. For each stick do:
891 for stick in all_sticks:
893 # Each stick has two atoms = two vertices.
896 [ 0,1 , 3,4 , 0,8 , 7,3]
897 [[0,1], [2,3], [4,5], [6,7]]
899 [ 0,1 , 3,4 , x,8 , 7,x] x:deleted
900 [[0,1], [2,3], [0,5], [6,2]]
903 # Check, if the vertex (atom) is already in the vertex list.
904 # edge: [s1,s2]
905 FLAG_s1 = False
906 s1 = 0
907 for stick2 in stick_vertices_nr:
908 if stick2 == stick.atom1-1:
909 FLAG_s1 = True
910 break
911 s1 += 1
912 FLAG_s2 = False
913 s2 = 0
914 for stick2 in stick_vertices_nr:
915 if stick2 == stick.atom2-1:
916 FLAG_s2 = True
917 break
918 s2 += 1
920 # If the vertex (atom) is not yet in the vertex list:
921 # append the number of atom and the vertex to the two lists.
922 # For the first atom:
923 if FLAG_s1 == False:
924 atom1 = copy(all_atoms[stick.atom1-1].location)
925 stick_vertices.append(atom1)
926 stick_vertices_nr.append(stick.atom1-1)
927 # For the second atom:
928 if FLAG_s2 == False:
929 atom2 = copy(all_atoms[stick.atom2-1].location)
930 stick_vertices.append(atom2)
931 stick_vertices_nr.append(stick.atom2-1)
933 # Build the edges:
935 # If both vertices (atoms) were not in the lists, then
936 # the edge is simply [i,i+1]. These are two new vertices
937 # (atoms), so increase i by 2.
938 if FLAG_s1 == False and FLAG_s2 == False:
939 stick_edges.append([i,i+1])
940 i += 2
941 # Both vertices (atoms) were already in the list, so then
942 # use the vertices (atoms), which already exist. They are
943 # at positions s1 and s2.
944 if FLAG_s1 == True and FLAG_s2 == True:
945 stick_edges.append([s1,s2])
946 # The following two if cases describe the situation that
947 # only one vertex (atom) was in the list. Since only ONE
948 # new vertex was added, increase i by one.
949 if FLAG_s1 == True and FLAG_s2 == False:
950 stick_edges.append([s1,i])
951 i += 1
952 if FLAG_s1 == False and FLAG_s2 == True:
953 stick_edges.append([i,s2])
954 i += 1
956 # Build the mesh of the sticks
957 stick_mesh = bpy.data.meshes.new("Mesh_sticks")
958 stick_mesh.from_pydata(stick_vertices, stick_edges, [])
959 stick_mesh.update()
960 new_stick_mesh = bpy.data.objects.new("Sticks", stick_mesh)
961 bpy.context.scene.objects.link(new_stick_mesh)
963 # Apply the skin modifier.
964 new_stick_mesh.modifiers.new(name="Sticks_skin", type='SKIN')
965 # Smooth the skin surface if this option has been chosen.
966 new_stick_mesh.modifiers[0].use_smooth_shade = use_sticks_smooth
967 # Apply the Subdivision modifier.
968 new_stick_mesh.modifiers.new(name="Sticks_subsurf", type='SUBSURF')
969 # Options: choose the levels
970 new_stick_mesh.modifiers[1].levels = sticks_subdiv_view
971 new_stick_mesh.modifiers[1].render_levels = sticks_subdiv_render
973 bpy.ops.object.material_slot_add()
974 stick_material = bpy.data.materials.new(ELEMENTS[-1].name)
975 stick_material.diffuse_color = ELEMENTS[-1].color
976 new_stick_mesh.active_material = stick_material
978 # This is for putting the radiu of the sticks onto
979 # the desired value 'Stick_diameter'
980 bpy.context.scene.objects.active = new_stick_mesh
981 # EDIT mode
982 bpy.ops.object.mode_set(mode='EDIT', toggle=False)
983 bm = bmesh.from_edit_mesh(new_stick_mesh.data)
984 bpy.ops.mesh.select_all(action='DESELECT')
986 # Select all vertices
987 for v in bm.verts:
988 v.select = True
990 # This is somewhat a factor for the radius.
991 r_f = 4.0
992 # Apply operator 'skin_resize'.
993 bpy.ops.transform.skin_resize(value=(Stick_diameter*r_f,
994 Stick_diameter*r_f,
995 Stick_diameter*r_f),
996 constraint_axis=(False, False, False),
997 constraint_orientation='GLOBAL',
998 mirror=False,
999 proportional='DISABLED',
1000 proportional_edit_falloff='SMOOTH',
1001 proportional_size=1,
1002 snap=False,
1003 snap_target='CLOSEST',
1004 snap_point=(0, 0, 0),
1005 snap_align=False,
1006 snap_normal=(0, 0, 0),
1007 texture_space=False,
1008 release_confirm=False)
1009 # Back to the OBJECT mode.
1010 bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
1012 return new_stick_mesh
1015 # Draw the sticks the normal way: connect the atoms by simple cylinders.
1016 # Two options: 1. single cylinders parented to an empty
1017 # 2. one single mesh object
1018 def draw_sticks_normal(all_atoms,
1019 all_sticks,
1020 center,
1021 Stick_diameter,
1022 Stick_sectors,
1023 use_sticks_smooth,
1024 use_sticks_one_object,
1025 use_sticks_one_object_nr):
1027 bpy.ops.object.material_slot_add()
1028 stick_material = bpy.data.materials.new(ELEMENTS[-1].name)
1029 stick_material.diffuse_color = ELEMENTS[-1].color
1031 up_axis = Vector([0.0, 0.0, 1.0])
1032 current_layers = bpy.context.scene.layers
1034 # For all sticks, do ...
1035 list_group = []
1036 list_group_sub = []
1037 counter = 0
1038 for stick in all_sticks:
1040 # The vectors of the two atoms
1041 atom1 = all_atoms[stick.atom1-1].location-center
1042 atom2 = all_atoms[stick.atom2-1].location-center
1043 # Location
1044 location = (atom1 + atom2) * 0.5
1045 # The difference of both vectors
1046 v = (atom2 - atom1)
1047 # Angle with respect to the z-axis
1048 angle = v.angle(up_axis, 0)
1049 # Cross-product between v and the z-axis vector. It is the
1050 # vector of rotation.
1051 axis = up_axis.cross(v)
1052 # Calculate Euler angles
1053 euler = Matrix.Rotation(angle, 4, axis).to_euler()
1054 # Create stick
1055 bpy.ops.mesh.primitive_cylinder_add(vertices=Stick_sectors,
1056 radius=Stick_diameter,
1057 depth=v.length,
1058 end_fill_type='NGON',
1059 view_align=False,
1060 enter_editmode=False,
1061 location=location,
1062 rotation=(0, 0, 0),
1063 layers=current_layers)
1064 # Put the stick into the scene ...
1065 stick = bpy.context.scene.objects.active
1066 # ... and rotate the stick.
1067 stick.rotation_euler = euler
1068 # ... and name
1069 stick.name = "Stick_Cylinder"
1070 counter += 1
1072 # Smooth the cylinder.
1073 if use_sticks_smooth == True:
1074 bpy.ops.object.select_all(action='DESELECT')
1075 stick.select = True
1076 bpy.ops.object.shade_smooth()
1078 list_group_sub.append(stick)
1080 if use_sticks_one_object == True:
1081 if counter == use_sticks_one_object_nr:
1082 bpy.ops.object.select_all(action='DESELECT')
1083 for stick in list_group_sub:
1084 stick.select = True
1085 bpy.ops.object.join()
1086 list_group.append(bpy.context.scene.objects.active)
1087 bpy.ops.object.select_all(action='DESELECT')
1088 list_group_sub = []
1089 counter = 0
1090 else:
1091 # Material ...
1092 stick.active_material = stick_material
1094 if use_sticks_one_object == True:
1095 bpy.ops.object.select_all(action='DESELECT')
1096 for stick in list_group_sub:
1097 stick.select = True
1098 bpy.ops.object.join()
1099 list_group.append(bpy.context.scene.objects.active)
1100 bpy.ops.object.select_all(action='DESELECT')
1102 for group in list_group:
1103 group.select = True
1104 bpy.ops.object.join()
1105 bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY',
1106 center='MEDIAN')
1107 sticks = bpy.context.scene.objects.active
1108 sticks.active_material = stick_material
1109 else:
1110 bpy.ops.object.empty_add(type='ARROWS',
1111 view_align=False,
1112 location=(0, 0, 0),
1113 rotation=(0, 0, 0),
1114 layers=current_layers)
1115 sticks = bpy.context.scene.objects.active
1116 for stick in list_group_sub:
1117 stick.parent = sticks
1119 sticks.name = "Sticks"
1120 sticks.location += center
1122 return sticks
1125 # -----------------------------------------------------------------------------
1126 # The main routine
1128 def import_pdb(Ball_type,
1129 Ball_azimuth,
1130 Ball_zenith,
1131 Ball_radius_factor,
1132 radiustype,
1133 Ball_distance_factor,
1134 use_sticks,
1135 use_sticks_type,
1136 sticks_subdiv_view,
1137 sticks_subdiv_render,
1138 use_sticks_color,
1139 use_sticks_smooth,
1140 use_sticks_bonds,
1141 use_sticks_one_object,
1142 use_sticks_one_object_nr,
1143 Stick_unit, Stick_dist,
1144 Stick_sectors,
1145 Stick_diameter,
1146 put_to_center,
1147 use_camera,
1148 use_light,
1149 filepath_pdb):
1152 # List of materials
1153 atom_material_list = []
1155 # A list of ALL objects which are loaded (needed for selecting the loaded
1156 # structure.
1157 atom_object_list = []
1159 # ------------------------------------------------------------------------
1160 # INITIALIZE THE ELEMENT LIST
1162 read_elements()
1164 # ------------------------------------------------------------------------
1165 # READING DATA OF ATOMS
1167 (Number_of_total_atoms, all_atoms) = read_pdb_file(filepath_pdb, radiustype)
1169 # ------------------------------------------------------------------------
1170 # MATERIAL PROPERTIES FOR ATOMS
1172 # The list that contains info about all types of atoms is created
1173 # here. It is used for building the material properties for
1174 # instance (see below).
1175 atom_all_types_list = []
1177 for atom in all_atoms:
1178 FLAG_FOUND = False
1179 for atom_type in atom_all_types_list:
1180 # If the atom name is already in the list, FLAG on 'True'.
1181 if atom_type[0] == atom.name:
1182 FLAG_FOUND = True
1183 break
1184 # No name in the current list has been found? => New entry.
1185 if FLAG_FOUND == False:
1186 # Stored are: Atom label (e.g. 'Na'), the corresponding atom
1187 # name (e.g. 'Sodium') and its color.
1188 atom_all_types_list.append([atom.name, atom.element, atom.color])
1190 # The list of materials is built.
1191 # Note that all atoms of one type (e.g. all hydrogens) get only ONE
1192 # material! This is good because then, by activating one atom in the
1193 # Blender scene and changing the color of this atom, one changes the color
1194 # of ALL atoms of the same type at the same time.
1196 # Create first a new list of materials for each type of atom
1197 # (e.g. hydrogen)
1198 for atom_type in atom_all_types_list:
1199 material = bpy.data.materials.new(atom_type[1])
1200 material.name = atom_type[0]
1201 material.diffuse_color = atom_type[2]
1202 atom_material_list.append(material)
1204 # Now, we go through all atoms and give them a material. For all atoms ...
1205 for atom in all_atoms:
1206 # ... and all materials ...
1207 for material in atom_material_list:
1208 # ... select the correct material for the current atom via
1209 # comparison of names ...
1210 if atom.name in material.name:
1211 # ... and give the atom its material properties.
1212 # However, before we check, if it is a vacancy, because then it
1213 # gets some additional preparation. The vacancy is represented
1214 # by a transparent cube.
1215 if atom.name == "Vacancy":
1216 material.transparency_method = 'Z_TRANSPARENCY'
1217 material.alpha = 1.3
1218 material.raytrace_transparency.fresnel = 1.6
1219 material.raytrace_transparency.fresnel_factor = 1.6
1220 material.use_transparency = True
1221 # The atom gets its properties.
1222 atom.material = material
1224 # ------------------------------------------------------------------------
1225 # READING DATA OF STICKS
1227 all_sticks = read_pdb_file_sticks(filepath_pdb,
1228 use_sticks_bonds,
1229 all_atoms)
1232 # So far, all atoms, sticks and materials have been registered.
1235 # ------------------------------------------------------------------------
1236 # TRANSLATION OF THE STRUCTURE TO THE ORIGIN
1238 # It may happen that the structure in a PDB file already has an offset
1239 # If chosen, the structure is first put into the center of the scene
1240 # (the offset is substracted).
1242 if put_to_center == True:
1243 sum_vec = Vector((0.0,0.0,0.0))
1244 # Sum of all atom coordinates
1245 sum_vec = sum([atom.location for atom in all_atoms], sum_vec)
1246 # Then the average is taken
1247 sum_vec = sum_vec / Number_of_total_atoms
1248 # After, for each atom the center of gravity is substracted
1249 for atom in all_atoms:
1250 atom.location -= sum_vec
1252 # ------------------------------------------------------------------------
1253 # SCALING
1255 # Take all atoms and adjust their radii and scale the distances.
1256 for atom in all_atoms:
1257 atom.location *= Ball_distance_factor
1259 # ------------------------------------------------------------------------
1260 # DETERMINATION OF SOME GEOMETRIC PROPERTIES
1262 # In the following, some geometric properties of the whole object are
1263 # determined: center, size, etc.
1264 sum_vec = Vector((0.0,0.0,0.0))
1266 # First the center is determined. All coordinates are summed up ...
1267 sum_vec = sum([atom.location for atom in all_atoms], sum_vec)
1269 # ... and the average is taken. This gives the center of the object.
1270 object_center_vec = sum_vec / Number_of_total_atoms
1272 # Now, we determine the size.The farthest atom from the object center is
1273 # taken as a measure. The size is used to place well the camera and light
1274 # into the scene.
1275 object_size_vec = [atom.location - object_center_vec for atom in all_atoms]
1276 object_size = max(object_size_vec).length
1278 # ------------------------------------------------------------------------
1279 # SORTING THE ATOMS
1281 # Lists of atoms of one type are created. Example:
1282 # draw_all_atoms = [ data_hydrogen,data_carbon,data_nitrogen ]
1283 # data_hydrogen = [["Hydrogen", Material_Hydrogen, Vector((x,y,z)), 109], ...]
1286 # Go through the list which contains all types of atoms. It is the list,
1287 # which has been created on the top during reading the PDB file.
1288 # Example: atom_all_types_list = ["hydrogen", "carbon", ...]
1289 draw_all_atoms = []
1290 for atom_type in atom_all_types_list:
1292 # Don't draw 'TER atoms'.
1293 if atom_type[0] == "TER":
1294 continue
1296 # This is the draw list, which contains all atoms of one type (e.g.
1297 # all hydrogens) ...
1298 draw_all_atoms_type = []
1300 # Go through all atoms ...
1301 for atom in all_atoms:
1302 # ... select the atoms of the considered type via comparison ...
1303 if atom.name == atom_type[0]:
1304 # ... and append them to the list 'draw_all_atoms_type'.
1305 draw_all_atoms_type.append([atom.name,
1306 atom.material,
1307 atom.location,
1308 atom.radius])
1310 # Now append the atom list to the list of all types of atoms
1311 draw_all_atoms.append(draw_all_atoms_type)
1313 # ------------------------------------------------------------------------
1314 # DRAWING THE ATOMS
1316 bpy.ops.object.select_all(action='DESELECT')
1318 # For each list of atoms of ONE type (e.g. Hydrogen)
1319 for draw_all_atoms_type in draw_all_atoms:
1321 atom_mesh = draw_atoms_one_type(draw_all_atoms_type,
1322 Ball_type,
1323 Ball_azimuth,
1324 Ball_zenith,
1325 Ball_radius_factor,
1326 object_center_vec)
1327 atom_object_list.append(atom_mesh)
1329 # ------------------------------------------------------------------------
1330 # DRAWING THE STICKS: cylinders in a dupliverts structure
1332 if use_sticks == True and use_sticks_type == '0' and all_sticks != []:
1334 sticks = draw_sticks_dupliverts(all_atoms,
1335 atom_all_types_list,
1336 object_center_vec,
1337 all_sticks,
1338 Stick_diameter,
1339 Stick_sectors,
1340 Stick_unit,
1341 Stick_dist,
1342 use_sticks_smooth,
1343 use_sticks_color)
1344 for stick in sticks:
1345 atom_object_list.append(stick)
1347 # ------------------------------------------------------------------------
1348 # DRAWING THE STICKS: skin and subdivision modifier
1350 if use_sticks == True and use_sticks_type == '1' and all_sticks != []:
1352 sticks = draw_sticks_skin(all_atoms,
1353 all_sticks,
1354 Stick_diameter,
1355 use_sticks_smooth,
1356 sticks_subdiv_view,
1357 sticks_subdiv_render)
1358 atom_object_list.append(sticks)
1360 # ------------------------------------------------------------------------
1361 # DRAWING THE STICKS: normal cylinders
1363 if use_sticks == True and use_sticks_type == '2' and all_sticks != []:
1365 sticks = draw_sticks_normal(all_atoms,
1366 all_sticks,
1367 object_center_vec,
1368 Stick_diameter,
1369 Stick_sectors,
1370 use_sticks_smooth,
1371 use_sticks_one_object,
1372 use_sticks_one_object_nr)
1373 atom_object_list.append(sticks)
1375 # ------------------------------------------------------------------------
1376 # CAMERA and LIGHT SOURCES
1378 camera_light_source(use_camera,
1379 use_light,
1380 object_center_vec,
1381 object_size)
1383 # ------------------------------------------------------------------------
1384 # SELECT ALL LOADED OBJECTS
1385 bpy.ops.object.select_all(action='DESELECT')
1386 obj = None
1387 for obj in atom_object_list:
1388 obj.select = True
1390 # activate the last selected object
1391 if obj:
1392 bpy.context.scene.objects.active = obj