Added XYZ animation frames #1

Open
John-Ferrier wants to merge 1 commits from John-Ferrier/io_mesh_atomic:main into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
3 changed files with 147 additions and 115 deletions

View File

@ -8,12 +8,12 @@
# Start of project : 2011-08-31 by CB # Start of project : 2011-08-31 by CB
# First publication in Blender : 2011-11-11 by CB # First publication in Blender : 2011-11-11 by CB
# Fusion of the PDB, XYZ and Panel : 2019-03-22 by CB # Fusion of the PDB, XYZ and Panel : 2019-03-22 by CB
# Last modified : 2024-05-28 by CB # Last modified : 2024-07-27 by JF
# #
# Contributing authors # Contributing authors
# ==================== # ====================
# # John Ferrier (ferrier.j@northeastern.edu)
# So far ... none ... . #
# #
# #
# Acknowledgements # Acknowledgements

View File

@ -27,7 +27,6 @@ from .xyz_export import export_xyz
class IMPORT_OT_xyz(Operator, ImportHelper): class IMPORT_OT_xyz(Operator, ImportHelper):
bl_idname = "import_mesh.xyz" bl_idname = "import_mesh.xyz"
bl_label = "Import XYZ (*.xyz)" bl_label = "Import XYZ (*.xyz)"
bl_description = "Import a XYZ atomic structure"
bl_options = {'PRESET', 'UNDO'} bl_options = {'PRESET', 'UNDO'}
filename_ext = ".xyz" filename_ext = ".xyz"
@ -162,7 +161,7 @@ class IMPORT_OT_xyz(Operator, ImportHelper):
self.use_center_all, self.use_center_all,
self.use_camera, self.use_camera,
self.use_lamp, self.use_lamp,
filepath_xyz) filepath_xyz, self.use_frames) # Added self.use_frames for animation purposes in xyz_import.py [ read_xyz_file() ]
# Load frames # Load frames
if len(ALL_FRAMES) > 1 and self.use_frames: if len(ALL_FRAMES) > 1 and self.use_frames:

View File

@ -4,6 +4,7 @@
import os import os
import bpy import bpy
import re
from math import pi, sqrt from math import pi, sqrt
from mathutils import Vector, Matrix from mathutils import Vector, Matrix
@ -192,144 +193,176 @@ def read_elements():
ELEMENTS.append(li) ELEMENTS.append(li)
def extract_number(file_name):
match = re.findall(r'\d+', file_name)
return int(''.join(match)) if match else 0
# filepath_pdb: path to pdb file # filepath_pdb: path to pdb file
# radiustype : '0' default # radiustype : '0' default
# '1' atomic radii # '1' atomic radii
# '2' van der Waals # '2' van der Waals
def read_xyz_file(filepath_xyz,radiustype): def read_xyz_file(filepath_xyz,radiustype, use_frames):
number_frames = 0 number_frames = 0
total_number_atoms = 0 total_number_atoms = 0
# Open the file ... #### Added .xyz sequence for animations! Original author has animation functions built in but
filepath_xyz_p = open(filepath_xyz, "r") # never allowed for ALL_FRAMES to be built out. Just added a functionality for ALL_FRAMES.
# Files need to be in the format of my_cool_file_1.xyz, my_cool_file_2.xyz, ..., my_cool_file_x.xyz
# No other numbers, as extract_number() puts all numbers together for sorting. Guess it could work if
# the pre-emptive numbers are all the same... This was mostly just a quick fix for my use-case though.
# - JF
#Go through the whole file. # Get the file directory
FLAG = False file_dir = os.path.dirname( filepath_xyz )
for line in filepath_xyz_p: files = []
# ... the loop is broken here (EOF) ... # If the use_frames option is selected on import, create a full list of the .xyz files in the dir
if line == "": if use_frames:
continue for file in os.listdir( file_dir ):
if file.endswith('.xyz'):
files.append( file )
split_list = line.rsplit() # Sort the list of .xyz files
files = sorted( files, key = extract_number )
if len(split_list) == 1: else:
number_atoms = int(split_list[0]) # If not, just append the single file
FLAG = True files.append( filepath_xyz )
if FLAG == True: # Cycle through each sorted file
for xyzf in files:
line = filepath_xyz_p.readline() # Open the file ...
line = line.rstrip() filepath_xyz_p = open( os.path.join( file_dir, xyzf ), "r")
all_atoms= [] #Go through the whole file.
for i in range(number_atoms): FLAG = False
for line in filepath_xyz_p:
# ... the loop is broken here (EOF) ...
if line == "":
continue
# This is a guarantee that only the total number of atoms of the split_list = line.rsplit()
# first frame is used. Condition is, so far, that the number of
# atoms in a xyz file is constant. However, sometimes the number
# may increase (or decrease). If it decreases, the addon crashes.
# If it increases, only the tot number of atoms of the first frame
# is used.
# By time, I will allow varying atom numbers ... but this takes
# some time ...
if number_frames != 0:
if i >= total_number_atoms:
break
if len(split_list) == 1:
number_atoms = int(split_list[0])
FLAG = True
if FLAG == True:
line = filepath_xyz_p.readline() line = filepath_xyz_p.readline()
line = line.rstrip() line = line.rstrip()
split_list = line.rsplit()
short_name = str(split_list[0])
# Go through all elements and find the element of the current atom. all_atoms= []
FLAG_FOUND = False for i in range(number_atoms):
for element in ELEMENTS:
if str.upper(short_name) == str.upper(element.short_name):
# Give the atom its proper name, color and radius:
name = element.name
# int(radiustype) => type of radius:
# pre-defined (0), atomic (1) or van der Waals (2)
radius = float(element.radii[int(radiustype)])
color = element.color
FLAG_FOUND = True
break
# Is it a vacancy or an 'unknown atom' ?
if FLAG_FOUND == False:
# Give this atom also a name. If it is an 'X' then it is a
# vacancy. Otherwise ...
if "X" in short_name:
short_name = "VAC"
name = "Vacancy"
radius = float(ELEMENTS[-3].radii[int(radiustype)])
color = ELEMENTS[-3].color
# ... take what is written in the xyz file. These are somewhat
# unknown atoms. This should never happen, the element list is
# almost complete. However, we do this due to security reasons.
else:
name = str.upper(short_name)
radius = float(ELEMENTS[-2].radii[int(radiustype)])
color = ELEMENTS[-2].color
x = float(split_list[1])
y = float(split_list[2])
z = float(split_list[3])
location = Vector((x,y,z))
all_atoms.append([short_name, name, location, radius, color])
# We note here all elements. This needs to be done only once.
if number_frames == 0:
# This is a guarantee that only the total number of atoms of the
# first frame is used. Condition is, so far, that the number of
# atoms in a xyz file is constant. However, sometimes the number
# may increase (or decrease). If it decreases, the addon crashes.
# If it increases, only the tot number of atoms of the first frame
# is used.
# By time, I will allow varying atom numbers ... but this takes
# some time ...
total_number_atoms = number_atoms
elements = [] # This is a guarantee that only the total number of atoms of the
for atom in all_atoms: # first frame is used. Condition is, so far, that the number of
# atoms in a xyz file is constant. However, sometimes the number
# may increase (or decrease). If it decreases, the addon crashes.
# If it increases, only the tot number of atoms of the first frame
# is used.
# By time, I will allow varying atom numbers ... but this takes
# some time ...
if number_frames != 0:
if i >= total_number_atoms:
break
line = filepath_xyz_p.readline()
line = line.rstrip()
split_list = line.rsplit()
short_name = str(split_list[0])
# Go through all elements and find the element of the current atom.
FLAG_FOUND = False FLAG_FOUND = False
for element in elements: for element in ELEMENTS:
# If the atom name is already in the list, if str.upper(short_name) == str.upper(element.short_name):
# FLAG on 'True'. # Give the atom its proper name, color and radius:
if element == atom[1]: name = element.name
# int(radiustype) => type of radius:
# pre-defined (0), atomic (1) or van der Waals (2)
radius = float(element.radii[int(radiustype)])
color = element.color
FLAG_FOUND = True FLAG_FOUND = True
break break
# No name in the current list has been found? => New entry.
# Is it a vacancy or an 'unknown atom' ?
if FLAG_FOUND == False: if FLAG_FOUND == False:
# Stored are: Atom label (e.g. 'Na'), the corresponding # Give this atom also a name. If it is an 'X' then it is a
# atom name (e.g. 'Sodium') and its color. # vacancy. Otherwise ...
elements.append(atom[1]) if "X" in short_name:
short_name = "VAC"
name = "Vacancy"
radius = float(ELEMENTS[-3].radii[int(radiustype)])
color = ELEMENTS[-3].color
# ... take what is written in the xyz file. These are somewhat
# unknown atoms. This should never happen, the element list is
# almost complete. However, we do this due to security reasons.
else:
name = str.upper(short_name)
radius = float(ELEMENTS[-2].radii[int(radiustype)])
color = ELEMENTS[-2].color
# Sort the atoms: create lists of atoms of one type x = float(split_list[1])
structure = [] y = float(split_list[2])
for element in elements: z = float(split_list[3])
atoms_one_type = []
for atom in all_atoms:
if atom[1] == element:
atoms_one_type.append(AtomProp(atom[0],
atom[1],
atom[2],
atom[3],
atom[4],[]))
structure.append(atoms_one_type)
ALL_FRAMES.append(structure) location = Vector((x,y,z))
number_frames += 1
FLAG = False
filepath_xyz_p.close() all_atoms.append([short_name, name, location, radius, color])
# We note here all elements. This needs to be done only once.
if number_frames == 0:
# This is a guarantee that only the total number of atoms of the
# first frame is used. Condition is, so far, that the number of
# atoms in a xyz file is constant. However, sometimes the number
# may increase (or decrease). If it decreases, the addon crashes.
# If it increases, only the tot number of atoms of the first frame
# is used.
# By time, I will allow varying atom numbers ... but this takes
# some time ...
total_number_atoms = number_atoms
elements = []
for atom in all_atoms:
FLAG_FOUND = False
for element in elements:
# If the atom name is already in the list,
# FLAG on 'True'.
if element == atom[1]:
FLAG_FOUND = True
break
# No name in the current list has been found? => New entry.
if FLAG_FOUND == False:
# Stored are: Atom label (e.g. 'Na'), the corresponding
# atom name (e.g. 'Sodium') and its color.
elements.append(atom[1])
# Sort the atoms: create lists of atoms of one type
structure = []
for element in elements:
atoms_one_type = []
for atom in all_atoms:
if atom[1] == element:
atoms_one_type.append(AtomProp(atom[0],
atom[1],
atom[2],
atom[3],
atom[4],[]))
structure.append(atoms_one_type)
ALL_FRAMES.append(structure)
number_frames += 1
FLAG = False
filepath_xyz_p.close()
return total_number_atoms return total_number_atoms
@ -441,7 +474,7 @@ def import_xyz(Ball_type,
put_to_center_all, put_to_center_all,
use_camera, use_camera,
use_light, use_light,
filepath_xyz): filepath_xyz, use_frames):
# List of materials # List of materials
atom_material_list = [] atom_material_list = []
@ -454,7 +487,7 @@ def import_xyz(Ball_type,
# ------------------------------------------------------------------------ # ------------------------------------------------------------------------
# READING DATA OF ATOMS # READING DATA OF ATOMS
Number_of_total_atoms = read_xyz_file(filepath_xyz, radiustype) Number_of_total_atoms = read_xyz_file(filepath_xyz, radiustype, use_frames)
# We show the atoms of the first frame. # We show the atoms of the first frame.
first_frame = ALL_FRAMES[0] first_frame = ALL_FRAMES[0]
@ -540,7 +573,7 @@ def import_xyz(Ball_type,
sum_vec = Vector((0.0,0.0,0.0)) sum_vec = Vector((0.0,0.0,0.0))
# Sum of all atom coordinates # Sum of all atom coordinates
for (i, atoms_of_one_type) in enumerate(frame): for i, atoms_of_one_type in enumerate(frame):
# This is a guarantee that only the total number of atoms of the # This is a guarantee that only the total number of atoms of the
# first frame is used. Condition is, so far, that the number of # first frame is used. Condition is, so far, that the number of