mirror of
https://github.com/blender/blender-addons-contrib.git
synced 2025-08-16 16:14:56 +00:00

have not been changed. I merely followed almost all advices on this page (rather cosmetics at the script): http://codereview.appspot.com/5369091/diff/1//dsk/data/src/blender/blender/release/scripts/addons/io_pdb_atomic.py For instance, I removed all 'Delete buttons', or I changed the layout of the buttons etc. in the panel, which looks more fancy now. I still need to do: - Entry in menue Import file. (I will do this the next days, should be easy) - This 'last active object' thing, Campbell is mentioning on the page above. (Also this will be done the next days) There are a few other changes - I removed a bug in the routine, which is responsible for scaling the radii of all atoms. - I introduced a new feature: One can scale all radii at once but now also invert this. There is a new button (Invert ...) and input field (Invert). All this helps to well adjust the radii of the atoms. Remarks: 1. I will include also an option that the radius of the sticks are modified at the same time. (This week) 2. The same will be done with atoms of one specific type. Cheers, Blendphys.
1219 lines
42 KiB
Python
1219 lines
42 KiB
Python
# ##### BEGIN GPL LICENSE BLOCK #####
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License
|
|
# as published by the Free Software Foundation; either version 2
|
|
# of the License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# The main author of the script is Dr. Clemens Barth.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software Foundation,
|
|
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
#
|
|
# ##### END GPL LICENSE BLOCK #####
|
|
|
|
bl_info = {
|
|
"name": "Atomic Blender",
|
|
"description": "Loading and manipulating atoms from PDB files",
|
|
"author": "Dr. Clemens Barth",
|
|
"version": (1,11),
|
|
"blender": (2,6),
|
|
"api": 31236,
|
|
"location": "Properties > Physics => Panel Atomic Blender",
|
|
"warning": "",
|
|
"wiki_url": "http://development.root-1.de/Atomic_Blender.php",
|
|
"tracker_url": "http://projects.blender.org/tracker/?func=detail&atid=467&aid=29226&group_id=153",
|
|
"category": "Import-Export"
|
|
}
|
|
|
|
import bpy
|
|
import io
|
|
import sys
|
|
import os
|
|
from math import *
|
|
import mathutils, math
|
|
from mathutils import Vector
|
|
#
|
|
PDBFILE = "PATH TO PDB FILE"
|
|
DATAFILE = "PATH TO DATA FILE"
|
|
|
|
Atomic_Blender_string = "Atomic Blender 1.1 -- Dr. Clemens Barth -- November 2011\n========================================================"
|
|
Atomic_Blender_panel_name = "PDB - Atomic Blender"
|
|
|
|
|
|
class OBJECT_PDB_Panel(bpy.types.Panel):
|
|
bl_label = Atomic_Blender_panel_name
|
|
bl_space_type = "PROPERTIES"
|
|
bl_region_type = "WINDOW"
|
|
bl_context = "physics"
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
scn = bpy.context.scene
|
|
|
|
row = layout.row()
|
|
layout.prop(scn, "pdb_filepath")
|
|
layout.prop(scn, "data_filepath")
|
|
row = layout.row()
|
|
|
|
|
|
row = layout.row()
|
|
row.prop(scn, "entry_group_atoms_yesno")
|
|
row.prop(scn, "entry_group_atoms_dn")
|
|
|
|
row = layout.row()
|
|
col = row.column(align=True)
|
|
col.prop(scn, "entry_mesh_yesno")
|
|
col.prop(scn, "entry_sectors_azimuth")
|
|
col.prop(scn, "entry_sectors_zenith")
|
|
col = row.column(align=True)
|
|
col.label(text="Scaling factors")
|
|
col.prop(scn, "entry_ball_radius")
|
|
col.prop(scn, "entry_distances")
|
|
col = row.column(align=True)
|
|
col.prop(scn, "entry_sticks_yesno")
|
|
col.prop(scn, "entry_sticks_sectors")
|
|
col.prop(scn, "entry_sticks_radius")
|
|
|
|
row = layout.row()
|
|
col = row.column(align=True)
|
|
col.prop(scn, "entry_scene_x")
|
|
col.prop(scn, "entry_scene_y")
|
|
col.prop(scn, "entry_scene_z")
|
|
col = row.column()
|
|
col.prop(scn, "entry_center_yesno")
|
|
|
|
row = layout.row(align=True)
|
|
col = row.column()
|
|
col.prop(scn, "entry_cam_yesno")
|
|
col.prop(scn, "entry_lamp_yesno")
|
|
col = row.column()
|
|
col.operator( "fp.button_start" )
|
|
row2 = col.row()
|
|
row2.label(text="Number of atoms")
|
|
row2.prop(scn, "entry_start_number_atoms")
|
|
|
|
|
|
|
|
if scn.entry_group_atoms_yesno == False:
|
|
|
|
row = layout.row()
|
|
row.operator( "fp.button_distance")
|
|
row.prop(scn, "entry_distance")
|
|
|
|
row = layout.row()
|
|
row.label(text="Modification of the radii of one type of atom")
|
|
row = layout.row()
|
|
col = row.column()
|
|
col.prop(scn, "entry_mod_pm_yesno")
|
|
col.prop(scn, "entry_mod_pm_radius")
|
|
col = row.column()
|
|
col.prop(scn, "entry_mod_rel_yesno")
|
|
col.prop(scn, "entry_mod_rel_radius")
|
|
col = row.column()
|
|
col.prop(scn, "entry_mod_atomname")
|
|
col.operator( "fp.button_modify" )
|
|
|
|
row = layout.row()
|
|
row.label(text="Modification of all atom radii")
|
|
row = layout.row()
|
|
row.prop(scn, "entry_mod_all_radii")
|
|
row.operator( "fp.button_modify_2" )
|
|
row = layout.row()
|
|
row.prop(scn, "entry_mod_all_radii_inv")
|
|
row.operator( "fp.button_modify_3" )
|
|
|
|
|
|
class Input_Output(bpy.types.PropertyGroup):
|
|
bpy.types.Scene.pdb_filepath = bpy.props.StringProperty(name = "PDB File", description="Path to the PDB file", maxlen = 256, default = PDBFILE, subtype='FILE_PATH', options={'HIDDEN'})
|
|
bpy.types.Scene.data_filepath = bpy.props.StringProperty(name = "DATA File", description="Path to the dat file", maxlen = 256, default = DATAFILE, subtype='FILE_PATH')
|
|
bpy.types.Scene.entry_group_atoms_yesno = bpy.props.BoolProperty (name = "Group atoms", default=False, description = "Grouping same type of atoms speeds up the loading of large-atom-PDB files")
|
|
bpy.types.Scene.entry_group_atoms_dn = bpy.props.IntProperty (name = "Delta N", default=200, min=0, description = "")
|
|
bpy.types.Scene.entry_mesh_yesno = bpy.props.BoolProperty (name = "Mesh balls", default=False, description = "Yes or no")
|
|
bpy.types.Scene.entry_sectors_azimuth = bpy.props.IntProperty (name = "Azimuth", default=32, min=0, description = "")
|
|
bpy.types.Scene.entry_sectors_zenith = bpy.props.IntProperty (name = "Zenith", default=32, min=0, description = "")
|
|
bpy.types.Scene.entry_ball_radius = bpy.props.FloatProperty (name = "Balls", default=1.0, min=0.0, description = "Ball radius")
|
|
bpy.types.Scene.entry_distances = bpy.props.FloatProperty (name = "Distances", default=1.0, min=0.0, description = "All distances")
|
|
bpy.types.Scene.entry_center_yesno = bpy.props.BoolProperty (name = "Object to origin", default=True, description = "Yes or no")
|
|
bpy.types.Scene.entry_scene_x = bpy.props.FloatProperty (name = "X", default=0.0, description = "X coordinate")
|
|
bpy.types.Scene.entry_scene_y = bpy.props.FloatProperty (name = "Y", default=0.0, description = "Y coordinate")
|
|
bpy.types.Scene.entry_scene_z = bpy.props.FloatProperty (name = "Z", default=0.0, description = "Z coordinate")
|
|
bpy.types.Scene.entry_sticks_yesno = bpy.props.BoolProperty (name = "Use sticks", default=False, description = "Shall sticks connect the atoms?")
|
|
bpy.types.Scene.entry_sticks_sectors = bpy.props.IntProperty (name = "Sector", default = 20, min=0, description = "Number of sectors of a stick")
|
|
bpy.types.Scene.entry_sticks_radius = bpy.props.FloatProperty (name = "Radius", default = 0.1, min=0.0, description = "Radius of a stick")
|
|
bpy.types.Scene.entry_cam_yesno = bpy.props.BoolProperty (name = "Camera", default=False, description = "Do you need a camera?")
|
|
bpy.types.Scene.entry_lamp_yesno = bpy.props.BoolProperty (name = "Lamp", default=False, description = "Do you need a lamp?")
|
|
bpy.types.Scene.entry_start_number_atoms = bpy.props.StringProperty(name = "", default="Number", description = "This output shows the number of atoms")
|
|
bpy.types.Scene.entry_distance = bpy.props.StringProperty(name = "", default="Distance", description = "Distance of 2 objects in Angstrom")
|
|
bpy.types.Scene.entry_mod_atomname = bpy.props.StringProperty(name = "", default = "Name of atom", description="Put in the name of the atom (e.g. Hydrogen)")
|
|
bpy.types.Scene.entry_mod_pm_yesno = bpy.props.BoolProperty (name = "Radius (pm)", default=False, description = "Modify the absolute value for the radius in pm")
|
|
bpy.types.Scene.entry_mod_pm_radius = bpy.props.FloatProperty (name = "", default = 100.0, min=0.0, description="Put in the radius of the atom (in pm)")
|
|
bpy.types.Scene.entry_mod_rel_yesno = bpy.props.BoolProperty (name = "Radius (scale)", default=False, description = "Scale the radius with a factor")
|
|
bpy.types.Scene.entry_mod_rel_radius = bpy.props.FloatProperty (name = "", default = 1.0, min=0.0, description="Put in the scale factor")
|
|
bpy.types.Scene.entry_mod_all_radii = bpy.props.FloatProperty (name = "Scale", default = 1.05, min=0.0, description="Put in the scale factor")
|
|
bpy.types.Scene.entry_mod_all_radii_inv = bpy.props.FloatProperty (name = "Invert", default = 0.95, min=0.0, description="Inverted scale factor")
|
|
|
|
|
|
class CLASS_Distance_Button(bpy.types.Operator):
|
|
bl_idname = "fp.button_distance"
|
|
bl_label = "Measure ..."
|
|
|
|
def execute(self, context):
|
|
dist = Measure_distance_in_scene()
|
|
|
|
if dist != "-1.0":
|
|
# The string length gets cut. Cut 3 digits after the '.' the string.
|
|
pos = str.find(dist, ".")
|
|
dist = dist[:pos+4]
|
|
|
|
# Actualization of the distance in the string input.
|
|
scn = bpy.context.scene
|
|
scn.entry_distance = dist
|
|
return {'FINISHED'}
|
|
|
|
|
|
class CLASS_Modify_Button(bpy.types.Operator):
|
|
bl_idname = "fp.button_modify"
|
|
bl_label = "Modify ..."
|
|
|
|
def execute(self, context):
|
|
scn = bpy.context.scene
|
|
atomname = scn.entry_mod_atomname
|
|
radius_pm = scn.entry_mod_pm_radius
|
|
radius_rel = scn.entry_mod_rel_radius
|
|
check_pm = scn.entry_mod_pm_yesno
|
|
check_rel = scn.entry_mod_rel_yesno
|
|
Modify_atom_radii_scene(atomname, radius_pm, check_pm, radius_rel, check_rel)
|
|
return {'FINISHED'}
|
|
|
|
|
|
class CLASS_Modify_Button_2(bpy.types.Operator):
|
|
bl_idname = "fp.button_modify_2"
|
|
bl_label = "Modify ..."
|
|
|
|
def execute(self, context):
|
|
scn = bpy.context.scene
|
|
scale = scn.entry_mod_all_radii
|
|
scn.entry_mod_all_radii_inv = 1.0/scale
|
|
Modify_atom_radii_scene_2(scale)
|
|
return {'FINISHED'}
|
|
|
|
|
|
class CLASS_Modify_Button_3(bpy.types.Operator):
|
|
bl_idname = "fp.button_modify_3"
|
|
bl_label = "Invert ..."
|
|
|
|
def execute(self, context):
|
|
scn = bpy.context.scene
|
|
scale = scn.entry_mod_all_radii_inv
|
|
Modify_atom_radii_scene_2(scale)
|
|
return {'FINISHED'}
|
|
|
|
|
|
class CLASS_Start_Button(bpy.types.Operator):
|
|
bl_idname = "fp.button_start"
|
|
bl_label = "DRAW THE OBJECT ..."
|
|
|
|
def execute(self, context):
|
|
scn = bpy.context.scene
|
|
|
|
azimuth = scn.entry_sectors_azimuth
|
|
zenith = scn.entry_sectors_zenith
|
|
bradius = scn.entry_ball_radius
|
|
bdistance = scn.entry_distances
|
|
center = scn.entry_center_yesno
|
|
x = scn.entry_scene_x
|
|
y = scn.entry_scene_y
|
|
z = scn.entry_scene_z
|
|
yn = scn.entry_sticks_yesno
|
|
ssector = scn.entry_sticks_sectors
|
|
sradius = scn.entry_sticks_radius
|
|
pdb = scn.pdb_filepath
|
|
data = scn.data_filepath
|
|
cam = scn.entry_cam_yesno
|
|
lamp = scn.entry_lamp_yesno
|
|
mesh = scn.entry_mesh_yesno
|
|
group = scn.entry_group_atoms_yesno
|
|
dn = scn.entry_group_atoms_dn
|
|
|
|
# This here is just to study this strange 'relative path' thing in the file dialog.
|
|
# Sometimes it doesn't work.
|
|
#print(scn.pdb_filepath)
|
|
#return {'FINISHED'}
|
|
|
|
atom_number = Draw_scene(group,dn,mesh,azimuth,zenith,bradius,bdistance,x,y,z,yn,ssector,sradius,center,cam,lamp,pdb,data)
|
|
scn.entry_start_number_atoms = atom_number
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
def register():
|
|
bpy.utils.register_class(OBJECT_PDB_Panel)
|
|
bpy.utils.register_class(Input_Output)
|
|
bpy.utils.register_class(CLASS_Start_Button)
|
|
bpy.utils.register_class(CLASS_Modify_Button)
|
|
bpy.utils.register_class(CLASS_Modify_Button_2)
|
|
bpy.utils.register_class(CLASS_Modify_Button_3)
|
|
bpy.utils.register_class(CLASS_Distance_Button)
|
|
|
|
|
|
def unregister():
|
|
bpy.utils.unregister_class(OBJECT_PDB_Panel)
|
|
bpy.utils.unregister_class(Input_Output)
|
|
bpy.utils.unregister_class(CLASS_Start_Button)
|
|
bpy.utils.unregister_class(CLASS_Modify_Button)
|
|
bpy.utils.unregister_class(CLASS_Modify_Button_2)
|
|
bpy.utils.unregister_class(CLASS_Modify_Button_3)
|
|
bpy.utils.unregister_class(CLASS_Distance_Button)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
register()
|
|
|
|
|
|
|
|
|
|
|
|
########################################################
|
|
#
|
|
#
|
|
#
|
|
#
|
|
#
|
|
#
|
|
# Some small routines
|
|
#
|
|
#
|
|
#
|
|
#
|
|
#
|
|
########################################################
|
|
|
|
|
|
|
|
# This function measures the distance between two objects (atoms), which are marked.
|
|
def Measure_distance_in_scene():
|
|
|
|
if len(bpy.context.selected_bases) > 1:
|
|
object_1 = bpy.context.selected_objects[0]
|
|
object_2 = bpy.context.selected_objects[1]
|
|
else:
|
|
return "-1.0"
|
|
|
|
v1 = object_1.location
|
|
v2 = object_2.location
|
|
dv = (v2 - v1)
|
|
length = str(dv.length)
|
|
return length
|
|
|
|
|
|
# Routine to modify the radii of a specific type of atom
|
|
def Modify_atom_radii_scene(atomname, radius_pm, check_pm, radius_rel, check_rel):
|
|
|
|
for obj in bpy.data.objects:
|
|
|
|
if atomname in obj.name:
|
|
|
|
if check_pm == True:
|
|
bpy.data.objects[obj.name].scale = (radius_pm/100,radius_pm/100,radius_pm/100)
|
|
if check_rel == True:
|
|
value = bpy.data.objects[obj.name].scale[0]
|
|
bpy.data.objects[obj.name].scale = (radius_rel * value,radius_rel * value,radius_rel * value)
|
|
|
|
|
|
# Routine to scale the radii of all atoms
|
|
def Modify_atom_radii_scene_2(scale):
|
|
|
|
for obj in bpy.data.objects:
|
|
|
|
if obj.type == "SURFACE" or obj.type == "MESH":
|
|
|
|
if "Stick" not in obj.name:
|
|
radius = obj.scale[0]
|
|
obj.scale = (radius * scale,radius * scale,radius * scale)
|
|
|
|
|
|
|
|
########################################################
|
|
#
|
|
#
|
|
#
|
|
#
|
|
#
|
|
# For reading the sticks inside the PDB file
|
|
#
|
|
#
|
|
#
|
|
#
|
|
#
|
|
########################################################
|
|
|
|
|
|
|
|
def Read_atom_for_stick(string):
|
|
|
|
string_length = len(string)
|
|
|
|
j = 0
|
|
string_reversed = ""
|
|
atoms = []
|
|
space = False
|
|
# An atom number can have max 5 letters
|
|
counter_letters = 5
|
|
|
|
for i in list(range(string_length)):
|
|
|
|
# If the 'T' of 'CONECT' is read => exit
|
|
if string[string_length-i-1] == 'T':
|
|
break
|
|
|
|
# Continue, if a space is read but no letter is present in 'string_reversed'.
|
|
# This happens, when there are spaces behind the last atom number in the
|
|
# string.
|
|
if string[string_length-i-1] == ' ' and string_reversed == "":
|
|
continue
|
|
|
|
if string[string_length-i-1] == ' ' or counter_letters == 0:
|
|
|
|
string_correct = ""
|
|
string_reversed_length = len(string_reversed)
|
|
l = 0
|
|
for k in list(range(string_reversed_length)):
|
|
string_correct = string_correct + string_reversed[string_reversed_length-k-1]
|
|
l += 1
|
|
|
|
# If the first 'space' is found, we found the number of an atom
|
|
# Transform the string into an integer and append this to the overall list
|
|
if space == False:
|
|
atom = int(string_correct)
|
|
atoms.append(atom)
|
|
# Initialization of the variables
|
|
string_reversed = ""
|
|
space = True
|
|
|
|
|
|
# If it was only a 'space' then go up the 'for loop'.
|
|
if counter_letters != 0:
|
|
counter_letters = 5
|
|
continue
|
|
|
|
counter_letters = 5
|
|
|
|
|
|
space = False
|
|
string_reversed = string_reversed + string[string_length-i-1]
|
|
j += 1
|
|
# One letter has been read, so one down with the counter.
|
|
# Max is 5!
|
|
counter_letters -= 1
|
|
|
|
# Return the list of atoms
|
|
return atoms
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
########################################################
|
|
#
|
|
#
|
|
#
|
|
#
|
|
#
|
|
# The main routine
|
|
#
|
|
#
|
|
#
|
|
#
|
|
#
|
|
########################################################
|
|
|
|
|
|
|
|
def Draw_scene(FLAG_group_atoms,group_atoms_dn,mesh_yn,Ball_azimuth,Ball_zenith,Ball_radius_factor,Ball_distance_factor,offset_x,offset_y,offset_z,Stick_yn,Stick_sectors,Stick_diameter, put_to_center, camera_yn, lamp_yn, Pdb_file, Data_file):
|
|
|
|
# This is in order to solve this strange 'relative path' thing.
|
|
Pdb_file = bpy.path.abspath(Pdb_file)
|
|
Data_file = bpy.path.abspath(Data_file)
|
|
|
|
|
|
# Lists for all atoms in the data file
|
|
Data_Number = []
|
|
Data_Atomname = []
|
|
Data_Shortname = []
|
|
Data_Color = []
|
|
Data_Radius = []
|
|
|
|
# Lists for atoms in the PDB file
|
|
atom_object = []
|
|
atom_element = []
|
|
atom_name = []
|
|
atom_charge = []
|
|
atom_color = []
|
|
atom_material = []
|
|
atom_x = []
|
|
atom_y = []
|
|
atom_z = []
|
|
atom_R = []
|
|
bar_atom1 = []
|
|
bar_atom2 = []
|
|
|
|
# Materials
|
|
atom_material_list = []
|
|
|
|
|
|
#
|
|
#
|
|
#
|
|
#
|
|
# READING DATA OF ALL POSSIBLE ATOMS FROM THE DATA FILE
|
|
#
|
|
#
|
|
#
|
|
#
|
|
|
|
|
|
# Read the data file, which contains all data (atom name, radii, colors, etc.)
|
|
Data_file_p = io.open(Data_file, "r")
|
|
|
|
i = 0
|
|
for line in Data_file_p:
|
|
|
|
if "Atom" in line:
|
|
|
|
line = Data_file_p.readline()
|
|
line = Data_file_p.readline()
|
|
pos = str.find(line, ":")
|
|
Data_Number.append(line[pos+2:-1])
|
|
|
|
line = Data_file_p.readline()
|
|
pos = str.find(line, ":")
|
|
Data_Atomname.append(line[pos+2:-1])
|
|
|
|
line = Data_file_p.readline()
|
|
pos = str.find(line, ":")
|
|
Data_Shortname.append(line[pos+2:-1])
|
|
|
|
line = Data_file_p.readline()
|
|
pos = str.find(line, ":")
|
|
color_value = line[pos+2:-1].split(',')
|
|
Data_Color.append([float(color_value[0]),float(color_value[1]),float(color_value[2])])
|
|
|
|
line = Data_file_p.readline()
|
|
pos = str.find(line, ":")
|
|
Data_Radius.append(line[pos+2:-1])
|
|
|
|
i += 1
|
|
|
|
Data_file_p.close()
|
|
|
|
all_existing_atoms = i
|
|
|
|
|
|
#
|
|
#
|
|
#
|
|
#
|
|
# READING DATA OF ATOMS
|
|
#
|
|
#
|
|
#
|
|
|
|
|
|
# Open the file ...
|
|
Pdb_file_p = io.open(Pdb_file, "r")
|
|
|
|
#Go to the line, in which "ATOM" or "HETATM" appears.
|
|
for line in Pdb_file_p:
|
|
split_list = line.split(' ')
|
|
if "ATOM" in split_list[0]:
|
|
break
|
|
if "HETATM" in split_list[0]:
|
|
break
|
|
|
|
# This is the list, which contains the names of all type of atoms.
|
|
atom_all_types_list = []
|
|
|
|
j = 0
|
|
# This is in fact an endless 'while loop', ...
|
|
while j > -1:
|
|
|
|
# ... the loop is broken here (EOF) ...
|
|
if line == "":
|
|
break
|
|
|
|
# If 'ATOM4 or 'HETATM' appears in the line then do ...
|
|
if "ATOM" in line or "HETATM" in line:
|
|
|
|
# Split the line into its parts (devided by a ' ') and analyse it. The first line is read.
|
|
split_list = line.rsplit()
|
|
|
|
for i in list(range(all_existing_atoms)):
|
|
if str.upper(split_list[-1]) == str.upper(Data_Shortname[i]):
|
|
# Give the atom its proper name and radius:
|
|
atom_element.append(str.upper(Data_Shortname[i]))
|
|
atom_name.append(Data_Atomname[i])
|
|
atom_R.append(float(Data_Radius[i]))
|
|
atom_color.append(Data_Color[i])
|
|
break
|
|
|
|
# 1. case: These are 'unknown' atoms. In some cases, atoms are named with an additional label like H1 (hydrogen1)
|
|
# 2. case: The last column 'split_list[-1]' does not exist, we take then column 3 in the PDB file.
|
|
if i == all_existing_atoms-1:
|
|
|
|
# Give this atom also a name. If it is an 'X' then it is a vacancy. Otherwise ...
|
|
if "X" in str.upper(split_list[2]):
|
|
atom_element.append("VAC")
|
|
atom_name.append("Vacancy")
|
|
# ... take what is written in the PDB file.
|
|
else:
|
|
atom_element.append(str.upper(split_list[2]))
|
|
atom_name.append(str.upper(split_list[2]))
|
|
|
|
# Default values for the atom.
|
|
atom_R.append(float(Data_Radius[all_existing_atoms-2]))
|
|
atom_color.append(Data_Color[all_existing_atoms-2])
|
|
|
|
|
|
|
|
# The list that contains all types of atoms is created here.
|
|
# If the name of the atom is already in the list, FLAG on 'found'.
|
|
FLAG_FOUND = False
|
|
for atom_type in atom_all_types_list:
|
|
if atom_type[0] == atom_name[-1]:
|
|
FLAG_FOUND = True
|
|
break
|
|
# No name in the current list has been found? => New entry.
|
|
if FLAG_FOUND == False:
|
|
atom_all_types_list.append([atom_name[-1],atom_element[-1],atom_color[-1]])
|
|
|
|
|
|
|
|
# 'coor' increases by 1 if 'x, y, z' are found
|
|
coor = 1
|
|
number = 0
|
|
|
|
for each_element in split_list:
|
|
|
|
# If there is a dot, it is an coordinate.
|
|
if "." in each_element:
|
|
if coor == 1:
|
|
atom_x.append(float(each_element))
|
|
coor += 1
|
|
elif coor == 2:
|
|
atom_y.append(float(each_element))
|
|
coor += 1
|
|
elif coor == 3:
|
|
atom_z.append(float(each_element))
|
|
coor += 1
|
|
|
|
j += 1
|
|
|
|
|
|
line = Pdb_file_p.readline()
|
|
line = line[:-1]
|
|
|
|
Pdb_file_p.close()
|
|
Number_of_total_atoms = j
|
|
|
|
|
|
#
|
|
#
|
|
#
|
|
#
|
|
# MATERIAL PROPERTIES FOR ATOMS
|
|
#
|
|
#
|
|
#
|
|
|
|
|
|
|
|
# Here, the atoms get already their material properties. Why already here? Because,
|
|
# then it is done and the atoms can be drawn in a fast way (see drawing part at the end
|
|
# of this script below).
|
|
|
|
# Create first a new list of materials for each type of atom (e.g. hydrogen)
|
|
for atom_type in atom_all_types_list:
|
|
|
|
bpy.ops.object.material_slot_add()
|
|
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 i in range(0, Number_of_total_atoms):
|
|
# ... and all materials ...
|
|
for material in atom_material_list:
|
|
# ... select the correct material for the current atom via name-comparison ...
|
|
if atom_name[i] in material.name:
|
|
# ... and give the atom its material properties. 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[i] == "Vacancy":
|
|
material.transparency_method = 'Z_TRANSPARENCY'
|
|
material.alpha = 1.3
|
|
material.raytrace_transparency.fresnel = 1.6
|
|
material.raytrace_transparency.fresnel_factor = 1.6
|
|
material.use_transparency = True
|
|
# The atom gets its properties.
|
|
atom_material.append(material)
|
|
|
|
|
|
#
|
|
#
|
|
#
|
|
#
|
|
# READING DATA OF STICKS
|
|
#
|
|
#
|
|
#
|
|
|
|
|
|
# Open the PDB file again such that the file pointer
|
|
# is in the first line ...
|
|
Pdb_file_p = io.open(Pdb_file, "r")
|
|
|
|
split_list = line.split(' ')
|
|
|
|
# Go to the first entry
|
|
if "CONECT" not in split_list[0]:
|
|
for line in Pdb_file_p:
|
|
split_list = line.split(' ')
|
|
if "CONECT" in split_list[0]:
|
|
break
|
|
|
|
|
|
Number_of_sticks = 0
|
|
doppelte_bars = 0
|
|
j = 0
|
|
# This is in fact an endless while loop, ...
|
|
while j > -1:
|
|
|
|
|
|
# ... which is broken here (EOF) ...
|
|
if line == "":
|
|
break
|
|
# ... or here, when no 'CONNECT' appears anymore.
|
|
if "CONECT" not in line:
|
|
break
|
|
|
|
if line[-1] == '\n':
|
|
line = line[:-1]
|
|
|
|
atoms_list = Read_atom_for_stick(line)
|
|
atoms_list_length = len(atoms_list)
|
|
|
|
q = 0
|
|
for each_element in atoms_list:
|
|
|
|
if q == atoms_list_length - 1:
|
|
break
|
|
|
|
atom1 = atoms_list[-1]
|
|
atom2 = each_element
|
|
|
|
FLAG_BAR = "all is okay"
|
|
|
|
for k in list(range(j)):
|
|
if (bar_atom1[k] == atom1 and bar_atom2[k] == atom2) or (bar_atom1[k] == atom2 and bar_atom2[k] == atom1):
|
|
doppelte_bars += 1
|
|
FLAG_BAR = "double!"
|
|
break
|
|
|
|
if FLAG_BAR == "all is okay":
|
|
bar_atom1.append(atom1)
|
|
bar_atom2.append(atom2)
|
|
Number_of_sticks += 1
|
|
j += 1
|
|
|
|
|
|
q += 1
|
|
|
|
line = Pdb_file_p.readline()
|
|
line = line[:-1]
|
|
|
|
Pdb_file_p.close()
|
|
# So far, all atoms and sticks have been registered.
|
|
|
|
|
|
|
|
#
|
|
#
|
|
#
|
|
#
|
|
# TRANSLATION OF THE OBJECT TO THE ORIGIN
|
|
#
|
|
#
|
|
#
|
|
|
|
|
|
|
|
# If chosen, the objects are first put into the center of the scene.
|
|
if put_to_center == True:
|
|
|
|
sum_x = 0
|
|
sum_y = 0
|
|
sum_z = 0
|
|
|
|
# Sum of all atom coordinates
|
|
for i in list(range(Number_of_total_atoms)):
|
|
|
|
sum_x = sum_x + atom_x[i]
|
|
sum_y = sum_y + atom_y[i]
|
|
sum_z = sum_z + atom_z[i]
|
|
|
|
# Then the average is taken
|
|
sum_x = sum_x / Number_of_total_atoms
|
|
sum_y = sum_y / Number_of_total_atoms
|
|
sum_z = sum_z / Number_of_total_atoms
|
|
|
|
# After, for each atom the center of gravity is substracted
|
|
for i in list(range(Number_of_total_atoms)):
|
|
|
|
atom_x[i] = atom_x[i] - sum_x
|
|
atom_y[i] = atom_y[i] - sum_y
|
|
atom_z[i] = atom_z[i] - sum_z
|
|
|
|
|
|
|
|
#
|
|
#
|
|
#
|
|
#
|
|
# SCALING GEOMETRIC PROPERTIES
|
|
#
|
|
#
|
|
#
|
|
|
|
|
|
# Take all atoms and ...
|
|
# - adjust their radii,
|
|
# - scale the distances,
|
|
# - and move the center of the whole ('+= offset_x', in Angstroem)
|
|
for i in list(range(Number_of_total_atoms)):
|
|
|
|
atom_charge.append(1.0)
|
|
atom_x[i] += offset_x
|
|
atom_y[i] += offset_y
|
|
atom_z[i] += offset_z
|
|
atom_x[i] *= Ball_distance_factor
|
|
atom_y[i] *= Ball_distance_factor
|
|
atom_z[i] *= Ball_distance_factor
|
|
|
|
|
|
|
|
#
|
|
#
|
|
#
|
|
#
|
|
# DETERMINATION OF SOME GEOMETRIC PROPERTIES
|
|
#
|
|
#
|
|
#
|
|
|
|
|
|
# In the following, some geometric properties of the whole object are
|
|
# determined: center, size, etc.
|
|
sum_x = 0
|
|
sum_y = 0
|
|
sum_z = 0
|
|
|
|
# First the center is determined. All coordinates are summed up ...
|
|
for i in list(range(Number_of_total_atoms)):
|
|
sum_x = sum_x + atom_x[i]
|
|
sum_y = sum_y + atom_y[i]
|
|
sum_z = sum_z + atom_z[i]
|
|
# ... and the average is taken. This gives the center of the object.
|
|
object_center = [sum_x / Number_of_total_atoms, sum_y / Number_of_total_atoms, sum_z / Number_of_total_atoms]
|
|
|
|
# Now, we determine the size. All coordinates are analyzed ...
|
|
object_size = 0.0
|
|
for i in list(range(Number_of_total_atoms)):
|
|
|
|
diff_x = atom_x[i] - object_center[0]
|
|
diff_y = atom_y[i] - object_center[1]
|
|
diff_z = atom_z[i] - object_center[2]
|
|
|
|
# This is needed in order to estimate the size of the object.
|
|
# The farest atom from the object center is taken as a measure.
|
|
distance_to_object_center = math.sqrt(diff_x*diff_x + diff_y*diff_y + diff_z*diff_z)
|
|
if distance_to_object_center > object_size:
|
|
object_size = distance_to_object_center
|
|
|
|
|
|
#
|
|
#
|
|
#
|
|
#
|
|
# CAMERA AND LAMP
|
|
#
|
|
#
|
|
#
|
|
|
|
dist = object_size / math.sqrt(object_size)
|
|
|
|
|
|
# Here a camera is put into the scene, if chosen.
|
|
if camera_yn == True:
|
|
|
|
camera_factor = 10.0
|
|
camera_x = object_center[0]+dist*camera_factor
|
|
camera_y = object_center[1]
|
|
camera_z = object_center[2]+dist*camera_factor
|
|
camera_pos = [camera_x,camera_y,camera_z]
|
|
bpy.ops.object.camera_add(view_align=False, enter_editmode=False, location=camera_pos, rotation=(0.0, 0.0, 0.0), layers=(True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False))
|
|
# Some properties of the camera are changed.
|
|
camera = bpy.data.objects['Camera'].data
|
|
bpy.data.objects['Camera'].name = "A_camera"
|
|
camera.name = "A_camera"
|
|
camera.lens = 45
|
|
camera.clip_end = 500.0
|
|
|
|
# Turning the camera
|
|
|
|
# The vector between camera and origin of the object
|
|
vec_cam_obj = (mathutils.Vector(camera_pos) - mathutils.Vector(object_center))
|
|
# A [0.0, 0.0, 1.0] vector along the z axis
|
|
vec_up_axis = mathutils.Vector([0.0, 0.0, 1.0])
|
|
# The angle between the last two vectors
|
|
angle = vec_cam_obj.angle(vec_up_axis, 0)
|
|
# The cross-product of the [0.0, 0.0, 1.0] vector and vec_cam_obj
|
|
axis = vec_up_axis.cross(vec_cam_obj)
|
|
euler = mathutils.Matrix.Rotation(angle, 4, axis).to_euler()
|
|
object = bpy.data.objects['A_camera']
|
|
object.rotation_euler = euler
|
|
|
|
|
|
|
|
# Here a lamp is put into the scene, if chosen.
|
|
if lamp_yn == True:
|
|
|
|
lamp_factor = 0.7
|
|
lamp_x = camera_x * lamp_factor
|
|
lamp_y = camera_y * lamp_factor + dist * camera_factor * lamp_factor * 0.2
|
|
lamp_z = camera_z * lamp_factor
|
|
lamp_pos = [lamp_x, lamp_y, lamp_z]
|
|
bpy.ops.object.lamp_add (type = 'POINT', view_align=False, location=lamp_pos, rotation=(0.0, 0.0, 0.0), layers=(True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False))
|
|
# Some properties of the lamp are changed.
|
|
lamp = bpy.data.objects['Point'].data
|
|
bpy.data.objects['Point'].name = "A_lamp"
|
|
lamp.name = "A_lamp"
|
|
lamp.distance = 500.0
|
|
lamp.energy = 2.0
|
|
lamp.shadow_method = 'RAY_SHADOW'
|
|
|
|
|
|
|
|
|
|
|
|
#
|
|
#
|
|
#
|
|
#
|
|
# SOME OUTPUT ON THE CONSOLE
|
|
#
|
|
#
|
|
#
|
|
|
|
|
|
# The following two loops give a huge printout in the terminal. If needed one can uncomment these lines
|
|
|
|
# Atoms
|
|
# print("\nCoordinates of the atoms:")
|
|
# for i in list(range(Number_of_total_atoms)):
|
|
# print(str(i+1) + " " + str(atom_x[i]) + " " + str(atom_y[i]) + " " + str(atom_z[i]) + " " + str(atom_R[i]) + " " + atom_element[i])
|
|
|
|
# Sticks
|
|
# print("\nSticks, which connect two atoms with indices:")
|
|
# for i in list(range(Number_of_sticks)):
|
|
# print(str(bar_atom1[i]) + " " + str(bar_atom2[i]))
|
|
|
|
print()
|
|
print()
|
|
print()
|
|
print(Atomic_Blender_string)
|
|
print()
|
|
print("Total number of atoms : " + str(Number_of_total_atoms))
|
|
print("Total number of sticks : " + str(Number_of_sticks))
|
|
print("Center of object : ", object_center)
|
|
print("Size of object : ", object_size)
|
|
print()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#
|
|
#
|
|
#
|
|
#
|
|
# DRAWING OF ATOMS
|
|
#
|
|
#
|
|
#
|
|
|
|
# This part was the main issue in earlier versions: the loading of atoms has taken too much time!
|
|
# Example: a surface can be easily composed of 5000 atoms => Loading 5000 NURBS needs quite a long time.
|
|
# There are two things I have done:
|
|
# 1. Remove any 'if's in the drawing loop
|
|
# 2. Group atoms of one type
|
|
|
|
# Lists of atoms of one type are first created. If it is atoms, all theses lists are put into
|
|
# 'draw_atom_type_list'. The vacancies have their extra list 'draw_atom_type_list_vacancy'
|
|
|
|
draw_atom_type_list = []
|
|
draw_atom_type_list_vacancy = []
|
|
for atom_type in atom_all_types_list:
|
|
|
|
# This is the draw list ...
|
|
draw_atom_list = []
|
|
|
|
for i in range(0, Number_of_total_atoms):
|
|
# ... select all atoms of one type ...
|
|
if atom_type[0] == atom_name[i]:
|
|
# ...
|
|
draw_atom_list.append([atom_name[i], atom_material[i], [atom_x[i], atom_y[i], atom_z[i]], atom_R[i]])
|
|
|
|
if atom_type[0] == "Vacancy":
|
|
draw_atom_type_list_vacancy.append(draw_atom_list)
|
|
else:
|
|
draw_atom_type_list.append(draw_atom_list)
|
|
|
|
|
|
#
|
|
# DRAW ATOMS
|
|
#
|
|
|
|
|
|
i = 0
|
|
bpy.ops.object.select_all(action='DESELECT')
|
|
# Draw NURBS or ...
|
|
if mesh_yn == False:
|
|
for atom_list in draw_atom_type_list:
|
|
group_counter = 0
|
|
groups = []
|
|
for atom in atom_list:
|
|
sys.stdout.write("Atom No. %d has been built\r" % (i+1) )
|
|
sys.stdout.flush()
|
|
|
|
bpy.ops.surface.primitive_nurbs_surface_sphere_add(view_align=False, enter_editmode=False, location=atom[2], rotation=(0.0, 0.0, 0.0), layers=(True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False))
|
|
aobject = bpy.context.scene.objects[0]
|
|
aobject.scale = (atom[3]*Ball_radius_factor,atom[3]*Ball_radius_factor,atom[3]*Ball_radius_factor)
|
|
aobject.name = atom[0]
|
|
aobject.active_material = atom[1]
|
|
atom_object.append(aobject)
|
|
|
|
i += 1
|
|
group_counter += 1
|
|
if FLAG_group_atoms == True:
|
|
if group_counter == group_atoms_dn:
|
|
for z in range(i-group_counter,i):
|
|
bpy.ops.object.select_name(name = atom_object[z].name, extend=True)
|
|
bpy.ops.object.join()
|
|
groups.append(bpy.context.scene.objects[0])
|
|
bpy.ops.object.select_all(action='DESELECT')
|
|
group_counter = 0
|
|
|
|
if FLAG_group_atoms == True:
|
|
for z in range(i-group_counter,i):
|
|
bpy.ops.object.select_name(name = atom_object[z].name, extend=True)
|
|
bpy.ops.object.join()
|
|
groups.append(bpy.context.scene.objects[0])
|
|
bpy.ops.object.select_all(action='DESELECT')
|
|
|
|
for group in groups:
|
|
bpy.ops.object.select_name(name = group.name, extend=True)
|
|
bpy.ops.object.join()
|
|
|
|
# ... draw Mesh balls
|
|
else:
|
|
for atom_list in draw_atom_type_list:
|
|
group_counter = 0
|
|
groups = []
|
|
for atom in atom_list:
|
|
sys.stdout.write("Atom No. %d has been built\r" % (i+1) )
|
|
sys.stdout.flush()
|
|
|
|
bpy.ops.mesh.primitive_uv_sphere_add(segments=Ball_azimuth, ring_count=Ball_zenith, size=1, view_align=False, enter_editmode=False, location=atom[2], rotation=(0, 0, 0), layers=(True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False))
|
|
aobject = bpy.context.scene.objects[0]
|
|
aobject.scale = (atom[3]*Ball_radius_factor,atom[3]*Ball_radius_factor,atom[3]*Ball_radius_factor)
|
|
aobject.name = atom[0]
|
|
aobject.active_material = atom[1]
|
|
atom_object.append(aobject)
|
|
|
|
i += 1
|
|
group_counter += 1
|
|
if FLAG_group_atoms == True:
|
|
if group_counter == group_atoms_dn:
|
|
for z in range(i-group_counter,i):
|
|
bpy.ops.object.select_name(name = atom_object[z].name, extend=True)
|
|
bpy.ops.object.join()
|
|
groups.append(bpy.context.scene.objects[0])
|
|
bpy.ops.object.select_all(action='DESELECT')
|
|
group_counter = 0
|
|
|
|
if FLAG_group_atoms == True:
|
|
for z in range(i-group_counter,i):
|
|
bpy.ops.object.select_name(name = atom_object[z].name, extend=True)
|
|
bpy.ops.object.join()
|
|
groups.append(bpy.context.scene.objects[0])
|
|
bpy.ops.object.select_all(action='DESELECT')
|
|
|
|
for group in groups:
|
|
bpy.ops.object.select_name(name = group.name, extend=True)
|
|
bpy.ops.object.join()
|
|
|
|
#
|
|
# DRAW VACANCIES
|
|
#
|
|
|
|
|
|
bpy.ops.object.select_all(action='DESELECT')
|
|
for atom_list in draw_atom_type_list_vacancy:
|
|
group_counter = 0
|
|
groups = []
|
|
for atom in atom_list:
|
|
sys.stdout.write("Atom No. %d has been built\r" % (i+1) )
|
|
sys.stdout.flush()
|
|
bpy.ops.mesh.primitive_cube_add(view_align=False, enter_editmode=False, location=atom[2], rotation=(0.0, 0.0, 0.0), layers=(True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False))
|
|
aobject = bpy.context.scene.objects[0]
|
|
aobject.scale = (atom[3]*Ball_radius_factor,atom[3]*Ball_radius_factor,atom[3]*Ball_radius_factor)
|
|
aobject.name = atom[0]
|
|
aobject.active_material = atom[1]
|
|
atom_object.append(aobject)
|
|
|
|
i += 1
|
|
group_counter += 1
|
|
if FLAG_group_atoms == True:
|
|
if group_counter == group_atoms_dn:
|
|
for z in range(i-group_counter,i):
|
|
bpy.ops.object.select_name(name = atom_object[z].name, extend=True)
|
|
bpy.ops.object.join()
|
|
groups.append(bpy.context.scene.objects[0])
|
|
bpy.ops.object.select_all(action='DESELECT')
|
|
group_counter = 0
|
|
if FLAG_group_atoms == True:
|
|
for z in range(i-group_counter,i):
|
|
bpy.ops.object.select_name(name = atom_object[z].name, extend=True)
|
|
bpy.ops.object.join()
|
|
groups.append(bpy.context.scene.objects[0])
|
|
bpy.ops.object.select_all(action='DESELECT')
|
|
|
|
for group in groups:
|
|
bpy.ops.object.select_name(name = group.name, extend=True)
|
|
bpy.ops.object.join()
|
|
|
|
print()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#
|
|
#
|
|
#
|
|
#
|
|
# DRAWING OF STICKS
|
|
#
|
|
#
|
|
#
|
|
|
|
|
|
|
|
if Stick_yn == True:
|
|
|
|
# Check first if the material already exists.
|
|
FLAG_FOUND = False
|
|
for material in bpy.data.materials:
|
|
|
|
# If the material with its color already exists in the material list, then
|
|
# take of course this material. Put the FLAG onto 'found!".
|
|
if material.name == "Stick":
|
|
stick_material = material
|
|
FLAG_FOUND = True
|
|
break
|
|
|
|
# If the FLAG is still 'False', a material with the color could not
|
|
# be found. Create a new material with the corresponding color. The
|
|
# color is taken from the all_atom list.
|
|
if FLAG_FOUND == False:
|
|
|
|
bpy.ops.object.material_slot_add()
|
|
stick_material = bpy.data.materials.new(Data_Shortname[all_existing_atoms-1])
|
|
stick_material.name = Data_Atomname[all_existing_atoms-1]
|
|
stick_material.diffuse_color = Data_Color [all_existing_atoms-1]
|
|
|
|
|
|
up_axis = mathutils.Vector([0.0, 0.0, 1.0])
|
|
|
|
for i in range(0,Number_of_sticks):
|
|
sys.stdout.write("Stick No. %d has been built\r" % (i+1) )
|
|
sys.stdout.flush()
|
|
k1 = mathutils.Vector([atom_x[bar_atom1[i]-1],atom_y[bar_atom1[i]-1],atom_z[bar_atom1[i]-1]])
|
|
k2 = mathutils.Vector([atom_x[bar_atom2[i]-1],atom_y[bar_atom2[i]-1],atom_z[bar_atom2[i]-1]])
|
|
v = (k2-k1)
|
|
angle = v.angle(up_axis, 0)
|
|
axis = up_axis.cross(v)
|
|
euler = mathutils.Matrix.Rotation(angle, 4, axis).to_euler()
|
|
bpy.ops.mesh.primitive_cylinder_add(vertices=Stick_sectors, radius=Stick_diameter, depth= v.length, cap_ends=True, view_align=False, enter_editmode=False, location= ((k1+k2)*0.5), rotation=(0,0,0), layers=(True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False))
|
|
stick = bpy.context.scene.objects[0]
|
|
stick.rotation_euler = euler
|
|
stick.active_material = stick_material
|
|
stick.name = Data_Atomname[all_existing_atoms-1]
|
|
|
|
print()
|
|
print()
|
|
print("All atoms and sticks have been drawn - finished.")
|
|
print()
|
|
print()
|
|
|
|
|
|
return str(Number_of_total_atoms)
|
|
|
|
|