molframe_core/structure/
overlay.rs1mod helpers;
4
5use super::materialize::compact_hierarchy;
6use super::{Structure, StructureData, validate, validate_coordinate_edit};
7use crate::annotation::AtomAnnotation;
8#[cfg(test)]
9use crate::bond::{BondRecord, BondTableBuilder};
10use crate::diagnostic::{Code, Diagnostic};
11use crate::index::ChainIndex;
12#[cfg(test)]
13use crate::index::{AtomIndex, ResidueIndex};
14use crate::selection::AtomSelection;
15use helpers::{delete_from, require_no_extensions, transform_store, validate_selection};
16
17const DELETED_ATOM: u32 = u32::MAX;
18
19#[derive(Debug)]
21pub struct StructureEditor {
22 data: StructureData,
23 coordinates_changed: bool,
24 requires_full_validation: bool,
25}
26
27impl Structure {
28 #[must_use]
30 pub fn edit(&self) -> StructureEditor {
31 StructureEditor {
32 data: self.data().clone(),
33 coordinates_changed: false,
34 requires_full_validation: false,
35 }
36 }
37
38 pub fn materialize(&self, selection: &AtomSelection) -> Result<Structure, Vec<Diagnostic>> {
52 if let Err(error) = validate_selection(selection, self.data().atom_count()) {
53 return Err(vec![error]);
54 }
55
56 let deleted = AtomSelection::All(self.atom_count()).difference(selection);
57 let mut editor = self.edit();
58 editor.clear_extensions();
59 if let Err(error) = editor.delete_atoms(&deleted) {
60 return Err(vec![error]);
61 }
62 if let Err(error) = compact_hierarchy(&mut editor.data) {
63 return Err(vec![error]);
64 }
65 editor.commit()
66 }
67}
68
69impl StructureEditor {
70 pub fn set_annotation(&mut self, name: &str, column: AtomAnnotation) -> Result<(), Diagnostic> {
77 if name.is_empty() {
78 return Err(Diagnostic::new(Code::E6007));
79 }
80 let atom_count = self.data.atom_count();
81 if column.len() != atom_count {
82 return Err(Diagnostic::new(Code::E3011)
83 .with_context("annotation", name)
84 .with_context("column rows", column.len().to_string())
85 .with_context("atoms", atom_count.to_string()));
86 }
87 let _ = self.data.annotations.insert(name, column);
88 self.requires_full_validation = true;
89 Ok(())
90 }
91
92 pub fn remove_annotation(&mut self, name: &str) -> bool {
94 let removed = self.data.annotations.remove(name).is_some();
95 self.requires_full_validation |= removed;
96 removed
97 }
98
99 pub fn clear_extensions(&mut self) {
101 self.data.extensions.clear();
102 self.requires_full_validation = true;
103 }
104
105 pub fn rename_chain(&mut self, chain: ChainIndex, label: &str) -> Result<(), Diagnostic> {
112 if self.data.topology.chains.label_asym_id(chain).is_none() {
113 return Err(Diagnostic::new(Code::E6006).with_context("chain", chain.to_string()));
114 }
115 if label.is_empty() {
116 return Err(Diagnostic::new(Code::E6007));
117 }
118 require_no_extensions(&self.data)?;
119 let symbol = self.data.dictionary.intern(label).map_err(|_| {
120 Diagnostic::new(Code::E1901)
121 .with_context("limit", "dictionary entries")
122 .with_context("identifier", label)
123 })?;
124 let result = self
125 .data
126 .topology
127 .chains
128 .rename(chain, symbol)
129 .map_err(|_| Diagnostic::new(Code::E6006).with_context("chain", chain.to_string()));
130 if result.is_ok() {
131 self.requires_full_validation = true;
132 }
133 result
134 }
135
136 pub fn transform<F>(
143 &mut self,
144 selection: &AtomSelection,
145 transform: F,
146 ) -> Result<(), Diagnostic>
147 where
148 F: Fn([f32; 3]) -> [f32; 3],
149 {
150 validate_selection(selection, self.data.atom_count())?;
151 if selection.is_empty() {
152 return Ok(());
153 }
154 let mut coords = self.data.coords.clone();
155 let changed = transform_store(&mut coords, selection, &transform)?;
156 if changed {
157 self.data.coords = coords;
158 self.coordinates_changed = true;
159 }
160 Ok(())
161 }
162
163 pub fn delete_atoms(&mut self, selection: &AtomSelection) -> Result<(), Diagnostic> {
169 validate_selection(selection, self.data.atom_count())?;
170 if selection.is_empty() {
171 return Ok(());
172 }
173 if matches!(&self.data.coords, super::CoordinateStore::Ragged { .. }) {
174 return Err(Diagnostic::new(Code::E6003));
175 }
176 require_no_extensions(&self.data)?;
177 self.data = delete_from(&self.data, selection)?;
178 self.coordinates_changed = true;
179 self.requires_full_validation = true;
180 Ok(())
181 }
182
183 pub fn commit(mut self) -> Result<Structure, Vec<Diagnostic>> {
190 if self.coordinates_changed || self.requires_full_validation {
191 let Some(generation) = self.data.generation.next() else {
192 return Err(vec![
193 Diagnostic::new(Code::E6003).with_context("coordinate_generation", "exhausted"),
194 ]);
195 };
196 self.data.generation = generation;
197 }
198 let findings = if self.requires_full_validation {
199 validate(&self.data)
200 } else {
201 validate_coordinate_edit(&self.data)
202 };
203 if findings.is_empty() {
204 Ok(Structure::new(self.data))
205 } else {
206 Err(findings)
207 }
208 }
209}
210
211#[cfg(test)]
212#[path = "overlay_tests.rs"]
213mod tests;