use chematic_core::{Atom, AtomIdx, BondOrder, Element, Molecule, MoleculeBuilder};
use crate::coords::{Coords3D, Point3};
fn covalent_radius(element: &Element) -> f64 {
match element.atomic_number() {
1 => 0.31, 6 => 0.77, 7 => 0.75, 8 => 0.73, 9 => 0.71, 15 => 1.07, 16 => 1.03, 17 => 0.99, 35 => 1.14, 53 => 1.33, _ => 0.77, }
}
#[derive(Debug, Clone)]
pub struct PdbAtom {
pub serial: u32,
pub name: String,
pub res_name: String,
pub chain_id: char,
pub res_seq: i32,
pub x: f64,
pub y: f64,
pub z: f64,
pub occupancy: f64,
pub temp_factor: f64,
pub element: String,
}
pub fn parse_pdb_atoms(input: &str) -> Vec<PdbAtom> {
let mut atoms = Vec::new();
for line in input.lines() {
let record = &line.get(0..6).unwrap_or("").trim_end();
if *record != "ATOM" && *record != "HETATM" {
continue;
}
let field = |start: usize, end: usize| -> &str {
let end = end.min(line.len());
if start >= end {
""
} else {
&line[start..end]
}
};
let serial = field(6, 11).trim().parse::<u32>().unwrap_or(0);
let name = field(12, 16).to_string();
let res_name = field(17, 20).trim().to_string();
let chain_id = field(21, 22).chars().next().unwrap_or(' ');
let res_seq = field(22, 26).trim().parse::<i32>().unwrap_or(0);
let x = field(30, 38).trim().parse::<f64>().unwrap_or(0.0);
let y = field(38, 46).trim().parse::<f64>().unwrap_or(0.0);
let z = field(46, 54).trim().parse::<f64>().unwrap_or(0.0);
let occupancy = field(54, 60).trim().parse::<f64>().unwrap_or(1.0);
let temp_factor = field(60, 66).trim().parse::<f64>().unwrap_or(0.0);
let element = field(76, 78).trim().to_string();
atoms.push(PdbAtom {
serial,
name,
res_name,
chain_id,
res_seq,
x,
y,
z,
occupancy,
temp_factor,
element,
});
}
atoms
}
pub fn pdb_to_molecule(atoms: &[PdbAtom]) -> (Molecule, Coords3D) {
let mut builder = MoleculeBuilder::new();
let mut points: Vec<Point3> = Vec::with_capacity(atoms.len());
let mut elements: Vec<Element> = Vec::with_capacity(atoms.len());
for pdb_atom in atoms {
let sym = if !pdb_atom.element.is_empty() {
pdb_atom.element.clone()
} else {
pdb_atom.name.trim().chars().next().unwrap_or('C').to_string()
};
let element = Element::from_symbol(&sym).unwrap_or(Element::C);
elements.push(element);
builder.add_atom(Atom::new(element));
points.push(Point3::new(pdb_atom.x, pdb_atom.y, pdb_atom.z));
}
let n = points.len();
for i in 0..n {
for j in (i + 1)..n {
let r_i = covalent_radius(&elements[i]);
let r_j = covalent_radius(&elements[j]);
let threshold = 1.3 * (r_i + r_j);
let dist = points[i].distance(&points[j]);
if dist < threshold {
let _ = builder.add_bond(AtomIdx(i as u32), AtomIdx(j as u32), BondOrder::Single);
}
}
}
let mol = builder.build();
let coords = Coords3D { points };
(mol, coords)
}
pub fn write_pdb(mol: &Molecule, coords: &Coords3D) -> String {
let mut out = String::new();
for i in 0..mol.atom_count() {
let idx = AtomIdx(i as u32);
let atom = mol.atom(idx);
let p = coords.get(idx);
let serial = (i + 1) as u32;
let elem_sym = atom.element.symbol();
let atom_name = if elem_sym.len() == 1 {
format!(" {:<3}", elem_sym)
} else {
format!("{:<4}", elem_sym)
};
let line = format!(
"{:<6}{:>5} {:<4} {:>3} {:1}{:>4} {:>8.3}{:>8.3}{:>8.3}{:>6.2}{:>6.2} {:>2}\n",
"HETATM", serial, atom_name, "LIG", 'A', 1_i32, p.x, p.y, p.z, 1.00_f64, 0.00_f64, elem_sym, );
out.push_str(&line);
}
out.push_str("END\n");
out
}