-
Clemens Barth authored
Dear all. 1. New option: sticks connecting atoms can have now the color of the atoms. 2. New button in the panel: 'Show sticks' scales the atom radii such that only the sticks are visible. 3. A bug was removed: Scaling atoms with a specific name scaled also the sticks. This does not happen anymore. - Due to the changes, the script has version 1.1 - To be done: Update of both Wiki pages (next days) - Probably, I commit a 2nd time today. Merry Christams, Blendphys
Clemens Barth authoredDear all. 1. New option: sticks connecting atoms can have now the color of the atoms. 2. New button in the panel: 'Show sticks' scales the atom radii such that only the sticks are visible. 3. A bug was removed: Scaling atoms with a specific name scaled also the sticks. This does not happen anymore. - Due to the changes, the script has version 1.1 - To be done: Update of both Wiki pages (next days) - Probably, I commit a 2nd time today. Merry Christams, Blendphys
__init__.py 21.46 KiB
# ##### 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.
#
# 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": "PDB Atomic Blender",
"description": "Loading and manipulating atoms from PDB files",
"author": "Clemens Barth",
"version": (1,1),
"blender": (2,6),
"api": 31236,
"location": "File -> Import -> PDB (.pdb), Panel: View 3D - Tools",
"warning": "",
"wiki_url": "http://development.root-1.de/Atomic_Blender.php",
"tracker_url": "http://projects.blender.org/tracker/"
"index.php?func=detail&aid=29226&group_id=153&atid=468",
"category": "Import-Export"
}
import bpy
from bpy.types import Operator, Panel
from bpy_extras.io_utils import ImportHelper
from bpy.props import (StringProperty,
BoolProperty,
EnumProperty,
IntProperty,
FloatProperty)
# TODO, allow reload
from . import import_pdb
# -----------------------------------------------------------------------------
# GUI
# The panel, which is loaded after the file has been
# chosen via the menu 'File -> Import'
class CLASS_atom_pdb_panel(Panel):
bl_label = "PDB - Atomic Blender"
#bl_space_type = "PROPERTIES"
#bl_region_type = "WINDOW"
#bl_context = "physics"
# This could be also an option ... :
bl_space_type = "VIEW_3D"
bl_region_type = "TOOL_PROPS"
# This 'poll thing' has taken 3 hours of a hard search and understanding.
# I explain it in the following from my point of view:
#
# Before this class is entirely treaten (here: drawing the panel) the
# poll method is called first. Basically, some conditions are
# checked before other things in the class are done afterwards. If a
# condition is not valid, one returns 'False' such that nothing further
# is done. 'True' means: 'Go on'
#
# In the case here, it is verified if the ATOM_PDB_FILEPATH variable contains
# a name. If not - and this is the case directly after having started the
# script - the panel does not appear because 'False' is returned. However,
# as soon as a file has been chosen, the panel appears because
# ATOM_PDB_FILEPATH contains a name.
#
# Please, correct me if I'm wrong.
@classmethod
def poll(self, context):
if import_pdb.ATOM_PDB_FILEPATH == "":
return False
else:
return True
def draw(self, context):
layout = self.layout
scn = bpy.context.scene
row = layout.row()
row.label(text="Outputs and custom data file")
box = layout.box()
row = box.row()
row.label(text="Custom data file")
row = box.row()
col = row.column()
col.prop(scn, "atom_pdb_datafile")
col.operator("atom_pdb.datafile_apply")
row = box.row()
col = row.column(align=True)
col.prop(scn, "atom_pdb_PDB_file")
row = layout.row()
row.label(text="Reload structure")
box = layout.box()
row = box.row()
col = row.column(align=True)
col.prop(scn, "use_atom_pdb_mesh")
col.prop(scn, "atom_pdb_mesh_azimuth")
col.prop(scn, "atom_pdb_mesh_zenith")
col = row.column(align=True)
col.label(text="Scaling factors")
col.prop(scn, "atom_pdb_scale_ballradius")
col.prop(scn, "atom_pdb_scale_distances")
row = box.row()
col = row.column()
col.prop(scn, "use_atom_pdb_sticks")
col = row.column(align=True)
col.prop(scn, "atom_pdb_sticks_sectors")
col.prop(scn, "atom_pdb_sticks_radius")
col.prop(scn, "use_atom_pdb_sticks_color")
row = box.row()
row.prop(scn, "use_atom_pdb_center")
row = box.row()
col = row.column()
col.prop(scn, "use_atom_pdb_cam")
col.prop(scn, "use_atom_pdb_lamp")
col = row.column()
col.operator("atom_pdb.button_reload")
# TODO, use lanel() instead
col.prop(scn, "atom_pdb_number_atoms")
row = box.row()
row.operator("atom_pdb.button_distance")
row.prop(scn, "atom_pdb_distance")
row = layout.row()
row.label(text="Modify atom radii")
box = layout.box()
row = box.row()
row.label(text="All changes concern:")
row = box.row()
row.prop(scn, "atom_pdb_radius_how")
row = box.row()
row.label(text="1. Change type of radii")
row = box.row()
row.prop(scn, "atom_pdb_radius_type")
row = box.row()
row.label(text="2. Change atom radii in pm")
row = box.row()
row.prop(scn, "atom_pdb_radius_pm_name")
row = box.row()
row.prop(scn, "atom_pdb_radius_pm")
row = box.row()
row.label(text="3. Change atom radii by scale")
row = box.row()
col = row.column()
col.prop(scn, "atom_pdb_radius_all")
col = row.column(align=True)
col.operator( "atom_pdb.radius_all_bigger" )
col.operator( "atom_pdb.radius_all_smaller" )
row = box.row()
row.label(text="4. Show sticks only")
row = box.row()
col = row.column()
col.operator( "atom_pdb.radius_sticks" )
if bpy.context.mode == 'EDIT_MESH':
row = layout.row()
row.label(text="Separate atom")
box = layout.box()
row = box.row()
row.operator( "atom_pdb.separate_atom" )
class CLASS_atom_pdb_IO(bpy.types.PropertyGroup):
def Callback_radius_type(self, context):
scnn = bpy.context.scene
import_pdb.DEF_atom_pdb_radius_type(
scnn.atom_pdb_radius_type,
scnn.atom_pdb_radius_how,
)
def Callback_radius_pm(self, context):
scnn = bpy.context.scene
import_pdb.DEF_atom_pdb_radius_pm(
scnn.atom_pdb_radius_pm_name,
scnn.atom_pdb_radius_pm,
scnn.atom_pdb_radius_how,
)
# In the file dialog window
scn = bpy.types.Scene
scn.use_atom_pdb_cam = BoolProperty(
name="Camera", default=False,
description="Do you need a camera?")
scn.use_atom_pdb_lamp = BoolProperty(
name="Lamp", default=False,
description = "Do you need a lamp?")
scn.use_atom_pdb_mesh = BoolProperty(
name = "Mesh balls", default=False,
description = "Do you want to use mesh balls instead of NURBS?")
scn.atom_pdb_mesh_azimuth = IntProperty(
name = "Azimuth", default=32, min=0,
description = "Number of sectors (azimuth)")
scn.atom_pdb_mesh_zenith = IntProperty(
name = "Zenith", default=32, min=0,
description = "Number of sectors (zenith)")
scn.atom_pdb_scale_ballradius = FloatProperty(
name = "Balls", default=1.0, min=0.0,
description = "Scale factor for all atom radii")
scn.atom_pdb_scale_distances = FloatProperty (
name = "Distances", default=1.0, min=0.0,
description = "Scale factor for all distances")
scn.use_atom_pdb_center = BoolProperty(
name = "Object to origin", default=True,
description = "Shall the object first put into the global origin "
"before applying the offsets on the left?")
scn.use_atom_pdb_sticks = BoolProperty(
name="Use sticks", default=False,
description="Do you want to display also the sticks?")
scn.atom_pdb_sticks_sectors = IntProperty(
name = "Sector", default=20, min=0,
description="Number of sectors of a stick")
scn.atom_pdb_sticks_radius = FloatProperty(
name = "Radius", default=0.1, min=0.0,
description ="Radius of a stick")
scn.use_atom_pdb_sticks_color = BoolProperty(
name="Color of atoms", default=False,
description="Shall the sticks appear in the color of the atoms?")
scn.atom_pdb_atomradius = EnumProperty(
name="Type of radius",
description="Choose type of atom radius",
items=(('0', "Pre-defined", "Use pre-defined radius"),
('1', "Atomic", "Use atomic radius"),
('2', "van der Waals", "Use van der Waals radius")),
default='0',)
# In the panel
scn.atom_pdb_datafile = StringProperty(
name = "", description="Path to your custom data file",
maxlen = 256, default = "", subtype='FILE_PATH')
scn.atom_pdb_PDB_file = StringProperty(
name = "Path to file", default="",
description = "Path of the PDB file")
# TODO, remove this property, its used for display only!
scn.atom_pdb_number_atoms = StringProperty(name="",
default="Number", description = "This output shows "
"the number of atoms which have been loaded")
scn.atom_pdb_distance = StringProperty(
name="", default="Distance (A)",
description="Distance of 2 objects in Angstrom")
scn.atom_pdb_radius_how = EnumProperty(
name="",
description="Which objects shall be modified?",
items=(('ALL_ACTIVE',"all active objects", "in the current layer"),
('ALL_IN_LAYER',"all"," in active layer(s)")),
default='ALL_ACTIVE',)
scn.atom_pdb_radius_type = EnumProperty(
name="Type",
description="Which type of atom radii?",
items=(('0',"predefined", "Use pre-defined radii"),
('1',"atomic", "Use atomic radii"),
('2',"van der Waals","Use van der Waals radii")),
default='0',update=Callback_radius_type)
scn.atom_pdb_radius_pm_name = StringProperty(
name="", default="Atom name",
description="Put in the name of the atom (e.g. Hydrogen)")
scn.atom_pdb_radius_pm = FloatProperty(
name="", default=100.0, min=0.0,
description="Put in the radius of the atom (in pm)",
update=Callback_radius_pm)
scn.atom_pdb_radius_all = FloatProperty(
name="Scale", default = 1.05, min=1.0,
description="Put in the scale factor")
# Button loading a custom data file
class CLASS_atom_pdb_datafile_apply(Operator):
bl_idname = "atom_pdb.datafile_apply"
bl_label = "Apply"
bl_description = "Use color and radii values stored in a custom file"
def execute(self, context):
scn = bpy.context.scene
if scn.atom_pdb_datafile == "":
return {'FINISHED'}
import_pdb.DEF_atom_pdb_custom_datafile(scn.atom_pdb_datafile)
# TODO, move this into 'import_pdb' and call the function
for obj in bpy.context.selected_objects:
if len(obj.children) != 0:
child = obj.children[0]
if child.type == "SURFACE" or child.type == "MESH":
for element in import_pdb.ATOM_PDB_ELEMENTS:
if element.name in obj.name:
child.scale = (element.radii[0],) * 3
child.active_material.diffuse_color = element.color
else:
if obj.type == "SURFACE" or obj.type == "MESH":
for element in import_pdb.ATOM_PDB_ELEMENTS:
if element.name in obj.name:
obj.scale = (element.radii[0],) * 3
obj.active_material.diffuse_color = element.color
return {'FINISHED'}
# Button for separating single objects from a atom mesh
class CLASS_atom_pdb_separate_atom(Operator):
bl_idname = "atom_pdb.separate_atom"
bl_label = "Separate atom"
bl_description = "Separate the atom you have chosen"
def execute(self, context):
scn = bpy.context.scene
# Get first all important properties from the atom which the user
# has chosen: location, color, scale
obj = bpy.context.edit_object
name = obj.name
loc_obj_vec = obj.location
scale = obj.children[0].scale
material = obj.children[0].active_material
# Separate the vertex from the main mesh and create a new mesh.
bpy.ops.mesh.separate()
new_object = bpy.context.scene.objects[0]
# Keep in mind the coordinates <= We only need this
loc_vec = new_object.data.vertices[0].co
# And now, switch to the OBJECT mode such that we can ...
bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
# ... delete the new mesh including the separated vertex
bpy.ops.object.select_all(action='DESELECT')
new_object.select = True
bpy.ops.object.delete() # TODO, use scene.objects.unlink()
# Create a new atom/vacancy at the position of the old atom
current_layers=bpy.context.scene.layers
if "Vacancy" not in name:
if scn.use_atom_pdb_mesh == False:
bpy.ops.surface.primitive_nurbs_surface_sphere_add(
view_align=False, enter_editmode=False,
location=loc_vec+loc_obj_vec,
rotation=(0.0, 0.0, 0.0),
layers=current_layers)
else:
bpy.ops.mesh.primitive_uv_sphere_add(
segments=scn.atom_pdb_mesh_azimuth,
ring_count=scn.atom_pdb_mesh_zenith,
size=1, view_align=False, enter_editmode=False,
location=loc_vec+loc_obj_vec,
rotation=(0, 0, 0),
layers=current_layers)
else:
bpy.ops.mesh.primitive_cube_add(
view_align=False, enter_editmode=False,
location=loc_vec+loc_obj_vec,
rotation=(0.0, 0.0, 0.0),
layers=current_layers)
new_atom = bpy.context.scene.objects.active
# Scale, material and name it.
new_atom.scale = scale
new_atom.active_material = material
new_atom.name = name + "_sep"
# Switch back into the 'Edit mode' because we would like to seprate
# other atoms may be (more convinient)
new_atom.select = False
obj.select = True
bpy.context.scene.objects.active = obj
bpy.ops.object.select_all(action='DESELECT')
bpy.ops.object.mode_set(mode='EDIT', toggle=False)
return {'FINISHED'}
# Button for measuring the distance of the active objects
class CLASS_atom_pdb_distance_button(Operator):
bl_idname = "atom_pdb.button_distance"
bl_label = "Measure ..."
bl_description = "Measure the distance between two objects"
def execute(self, context):
scn = bpy.context.scene
dist = import_pdb.DEF_atom_pdb_distance()
if dist != "N.A.":
# The string length is cut, 3 digits after the first 3 digits
# after the '.'. Append also "Angstrom".
# Remember: 1 Angstrom = 10^(-10) m
pos = str.find(dist, ".")
dist = dist[:pos+4]
dist = dist + " A"
# Put the distance into the string of the output field.
scn.atom_pdb_distance = dist
return {'FINISHED'}
# Button for increasing the radii of all atoms
class CLASS_atom_pdb_radius_all_bigger_button(Operator):
bl_idname = "atom_pdb.radius_all_bigger"
bl_label = "Bigger ..."
bl_description = "Increase the radii of the atoms"
def execute(self, context):
scn = bpy.context.scene
import_pdb.DEF_atom_pdb_radius_all(
scn.atom_pdb_radius_all,
scn.atom_pdb_radius_how,
)
return {'FINISHED'}
# Button for decreasing the radii of all atoms
class CLASS_atom_pdb_radius_all_smaller_button(Operator):
bl_idname = "atom_pdb.radius_all_smaller"
bl_label = "Smaller ..."
bl_description = "Decrease the radii of the atoms"
def execute(self, context):
scn = bpy.context.scene
import_pdb.DEF_atom_pdb_radius_all(
1.0/scn.atom_pdb_radius_all,
scn.atom_pdb_radius_how,
)
return {'FINISHED'}
# Button for showing the sticks only - the radii of the atoms have the radius
# of the sticks
class CLASS_atom_pdb_radius_sticks_button(Operator):
bl_idname = "atom_pdb.radius_sticks"
bl_label = "Show sticks"
bl_description = "Show only the sticks (atom radii = stick radii)"
def execute(self, context):
scn = bpy.context.scene
import_pdb.DEF_atom_pdb_radius_sticks(
scn.atom_pdb_sticks_radius,
scn.atom_pdb_radius_how,
)
return {'FINISHED'}
# The button for reloading the atoms and creating the scene
class CLASS_atom_pdb_load_button(Operator):
bl_idname = "atom_pdb.button_reload"
bl_label = "RELOAD"
bl_description = "Load the structure again"
def execute(self, context):
scn = bpy.context.scene
azimuth = scn.atom_pdb_mesh_azimuth
zenith = scn.atom_pdb_mesh_zenith
bradius = scn.atom_pdb_scale_ballradius
bdistance = scn.atom_pdb_scale_distances
radiustype = scn.atom_pdb_atomradius
center = scn.use_atom_pdb_center
sticks = scn.use_atom_pdb_sticks
sticks_col = scn.use_atom_pdb_sticks_color
ssector = scn.atom_pdb_sticks_sectors
sradius = scn.atom_pdb_sticks_radius
cam = scn.use_atom_pdb_cam
lamp = scn.use_atom_pdb_lamp
mesh = scn.use_atom_pdb_mesh
datafile = scn.atom_pdb_datafile
# Execute main routine an other time ... from the panel
atom_number = import_pdb.DEF_atom_pdb_main(
mesh, azimuth, zenith, bradius,
radiustype, bdistance, sticks, sticks_col,
ssector, sradius, center, cam, lamp, datafile,
)
scn.atom_pdb_number_atoms = str(atom_number) + " atoms"
return {'FINISHED'}
# This is the class for the file dialog.
class ImportPDB(Operator, ImportHelper):
bl_idname = "import_mesh.pdb"
bl_label = "Import Protein Data Bank(*.pdb)"
filename_ext = ".pdb"
filter_glob = StringProperty(default="*.pdb", options={'HIDDEN'},)
def draw(self, context):
layout = self.layout
scn = bpy.context.scene
row = layout.row()
row.prop(scn, "use_atom_pdb_cam")
row.prop(scn, "use_atom_pdb_lamp")
row = layout.row()
col = row.column()
col.prop(scn, "use_atom_pdb_mesh")
col = row.column(align=True)
col.prop(scn, "atom_pdb_mesh_azimuth")
col.prop(scn, "atom_pdb_mesh_zenith")
row = layout.row()
col = row.column()
col.label(text="Scaling factors")
col = row.column(align=True)
col.prop(scn, "atom_pdb_scale_ballradius")
col.prop(scn, "atom_pdb_scale_distances")
row = layout.row()
col = row.column()
col.prop(scn, "use_atom_pdb_sticks")
col = row.column(align=True)
col.prop(scn, "atom_pdb_sticks_sectors")
col.prop(scn, "atom_pdb_sticks_radius")
col.prop(scn, "use_atom_pdb_sticks_color")
row = layout.row()
row.prop(scn, "use_atom_pdb_center")
row = layout.row()
row.prop(scn, "atom_pdb_atomradius")
def execute(self, context):
scn = bpy.context.scene
# This is in order to solve this strange 'relative path' thing.
import_pdb.ATOM_PDB_FILEPATH = bpy.path.abspath(self.filepath)
scn.atom_pdb_PDB_file = import_pdb.ATOM_PDB_FILEPATH
azimuth = scn.atom_pdb_mesh_azimuth
zenith = scn.atom_pdb_mesh_zenith
bradius = scn.atom_pdb_scale_ballradius
bdistance = scn.atom_pdb_scale_distances
radiustype = scn.atom_pdb_atomradius
center = scn.use_atom_pdb_center
sticks = scn.use_atom_pdb_sticks
sticks_col = scn.use_atom_pdb_sticks_color
ssector = scn.atom_pdb_sticks_sectors
sradius = scn.atom_pdb_sticks_radius
cam = scn.use_atom_pdb_cam
lamp = scn.use_atom_pdb_lamp
mesh = scn.use_atom_pdb_mesh
datafile = scn.atom_pdb_datafile
# Execute main routine
atom_number = import_pdb.DEF_atom_pdb_main(
mesh, azimuth, zenith, bradius,
radiustype, bdistance, sticks, sticks_col,
ssector, sradius, center, cam, lamp, datafile)
scn.atom_pdb_number_atoms = str(atom_number) + " atoms"
return {'FINISHED'}
# The entry into the menu 'file -> import'
def menu_func(self, context):
self.layout.operator(ImportPDB.bl_idname, text="Protein Data Bank (.pdb)")
def register():
bpy.utils.register_module(__name__)
bpy.types.INFO_MT_file_import.append(menu_func)
def unregister():
bpy.utils.unregister_module(__name__)
bpy.types.INFO_MT_file_import.remove(menu_func)
if __name__ == "__main__":
register()