mod remap;
use super::materialize::compact_hierarchy;
use super::{Structure, StructureData, validate, validate_coordinate_edit};
use crate::annotation::AtomAnnotation;
#[cfg(test)]
use crate::bond::{BondRecord, BondTableBuilder};
use crate::diagnostic::{Code, Diagnostic};
use crate::index::ChainIndex;
#[cfg(test)]
use crate::index::{AtomIndex, ResidueIndex};
use crate::selection::AtomSelection;
use remap::{delete_from, require_no_extensions, transform_store, validate_selection};
const DELETED_ATOM: u32 = u32::MAX;
#[derive(Debug)]
pub struct StructureEditor {
data: StructureData,
coordinates_changed: bool,
requires_full_validation: bool,
}
impl Structure {
#[must_use]
pub fn edit(&self) -> StructureEditor {
StructureEditor {
data: self.data().clone(),
coordinates_changed: false,
requires_full_validation: false,
}
}
pub fn materialize(&self, selection: &AtomSelection) -> Result<Structure, Vec<Diagnostic>> {
if let Err(error) = validate_selection(selection, self.data().atom_count()) {
return Err(vec![error]);
}
let deleted = AtomSelection::All(self.atom_count()).difference(selection);
let mut editor = self.edit();
editor.clear_extensions();
if let Err(error) = editor.delete_atoms(&deleted) {
return Err(vec![error]);
}
if let Err(error) = compact_hierarchy(&mut editor.data) {
return Err(vec![error]);
}
editor.commit()
}
}
impl StructureEditor {
pub fn set_annotation(&mut self, name: &str, column: AtomAnnotation) -> Result<(), Diagnostic> {
if name.is_empty() {
return Err(Diagnostic::new(Code::E6007));
}
let atom_count = self.data.atom_count();
if column.len() != atom_count {
return Err(Diagnostic::new(Code::E3011)
.with_context("annotation", name)
.with_context("column rows", column.len().to_string())
.with_context("atoms", atom_count.to_string()));
}
let _ = self.data.annotations.insert(name, column);
self.requires_full_validation = true;
Ok(())
}
pub fn remove_annotation(&mut self, name: &str) -> bool {
let removed = self.data.annotations.remove(name).is_some();
self.requires_full_validation |= removed;
removed
}
pub fn clear_extensions(&mut self) {
self.data.extensions.clear();
self.requires_full_validation = true;
}
pub fn rename_chain(&mut self, chain: ChainIndex, label: &str) -> Result<(), Diagnostic> {
if self.data.topology.chains.label_asym_id(chain).is_none() {
return Err(Diagnostic::new(Code::E6006).with_context("chain", chain.to_string()));
}
if label.is_empty() {
return Err(Diagnostic::new(Code::E6007));
}
require_no_extensions(&self.data)?;
let symbol = self.data.dictionary.intern(label).map_err(|_| {
Diagnostic::new(Code::E1901)
.with_context("limit", "dictionary entries")
.with_context("identifier", label)
})?;
let result = self
.data
.topology
.chains
.rename(chain, symbol)
.map_err(|_| Diagnostic::new(Code::E6006).with_context("chain", chain.to_string()));
if result.is_ok() {
self.requires_full_validation = true;
}
result
}
pub fn transform<F>(
&mut self,
selection: &AtomSelection,
transform: F,
) -> Result<(), Diagnostic>
where
F: Fn([f32; 3]) -> [f32; 3],
{
validate_selection(selection, self.data.atom_count())?;
if selection.is_empty() {
return Ok(());
}
let mut coords = self.data.coords.clone();
let changed = transform_store(&mut coords, selection, &transform)?;
if changed {
self.data.coords = coords;
self.coordinates_changed = true;
}
Ok(())
}
pub fn delete_atoms(&mut self, selection: &AtomSelection) -> Result<(), Diagnostic> {
validate_selection(selection, self.data.atom_count())?;
if selection.is_empty() {
return Ok(());
}
if matches!(&self.data.coords, super::CoordinateStore::Ragged { .. }) {
return Err(Diagnostic::new(Code::E6003));
}
require_no_extensions(&self.data)?;
self.data = delete_from(&self.data, selection)?;
self.coordinates_changed = true;
self.requires_full_validation = true;
Ok(())
}
pub fn commit(mut self) -> Result<Structure, Vec<Diagnostic>> {
if self.coordinates_changed || self.requires_full_validation {
let Some(generation) = self.data.generation.next() else {
return Err(vec![
Diagnostic::new(Code::E6003).with_context("coordinate_generation", "exhausted"),
]);
};
self.data.generation = generation;
}
let findings = if self.requires_full_validation {
validate(&self.data)
} else {
validate_coordinate_edit(&self.data)
};
if findings.is_empty() {
Ok(Structure::new(self.data))
} else {
Err(findings)
}
}
}
#[cfg(test)]
#[path = "overlay_tests.rs"]
mod tests;