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>> {
49 if let Err(error) = validate_selection(selection, self.data().atom_count()) {
50 return Err(vec![error]);
51 }
52
53 let deleted = AtomSelection::All(self.atom_count()).difference(selection);
54 let mut editor = self.edit();
55 editor.clear_extensions();
56 if let Err(error) = editor.delete_atoms(&deleted) {
57 return Err(vec![error]);
58 }
59 if let Err(error) = compact_hierarchy(&mut editor.data) {
60 return Err(vec![error]);
61 }
62 editor.commit()
63 }
64}
65
66impl StructureEditor {
67 pub fn set_annotation(&mut self, name: &str, column: AtomAnnotation) -> Result<(), Diagnostic> {
74 if name.is_empty() {
75 return Err(Diagnostic::new(Code::E6007));
76 }
77 let atom_count = self.data.atom_count();
78 if column.len() != atom_count {
79 return Err(Diagnostic::new(Code::E3011)
80 .with_context("annotation", name)
81 .with_context("column rows", column.len().to_string())
82 .with_context("atoms", atom_count.to_string()));
83 }
84 let _ = self.data.annotations.insert(name, column);
85 self.requires_full_validation = true;
86 Ok(())
87 }
88
89 pub fn remove_annotation(&mut self, name: &str) -> bool {
91 let removed = self.data.annotations.remove(name).is_some();
92 self.requires_full_validation |= removed;
93 removed
94 }
95
96 pub fn clear_extensions(&mut self) {
98 self.data.extensions.clear();
99 self.requires_full_validation = true;
100 }
101
102 pub fn rename_chain(&mut self, chain: ChainIndex, label: &str) -> Result<(), Diagnostic> {
109 if self.data.topology.chains.label_asym_id(chain).is_none() {
110 return Err(Diagnostic::new(Code::E6006).with_context("chain", chain.to_string()));
111 }
112 if label.is_empty() {
113 return Err(Diagnostic::new(Code::E6007));
114 }
115 require_no_extensions(&self.data)?;
116 let symbol = self.data.dictionary.intern(label).map_err(|_| {
117 Diagnostic::new(Code::E1901)
118 .with_context("limit", "dictionary entries")
119 .with_context("identifier", label)
120 })?;
121 let result = self
122 .data
123 .topology
124 .chains
125 .rename(chain, symbol)
126 .map_err(|_| Diagnostic::new(Code::E6006).with_context("chain", chain.to_string()));
127 if result.is_ok() {
128 self.requires_full_validation = true;
129 }
130 result
131 }
132
133 pub fn transform<F>(
140 &mut self,
141 selection: &AtomSelection,
142 transform: F,
143 ) -> Result<(), Diagnostic>
144 where
145 F: Fn([f32; 3]) -> [f32; 3],
146 {
147 validate_selection(selection, self.data.atom_count())?;
148 if selection.is_empty() {
149 return Ok(());
150 }
151 let mut coords = self.data.coords.clone();
152 let changed = transform_store(&mut coords, selection, &transform)?;
153 if changed {
154 self.data.coords = coords;
155 self.coordinates_changed = true;
156 }
157 Ok(())
158 }
159
160 pub fn delete_atoms(&mut self, selection: &AtomSelection) -> Result<(), Diagnostic> {
166 validate_selection(selection, self.data.atom_count())?;
167 if selection.is_empty() {
168 return Ok(());
169 }
170 if matches!(&self.data.coords, super::CoordinateStore::Ragged { .. }) {
171 return Err(Diagnostic::new(Code::E6003));
172 }
173 require_no_extensions(&self.data)?;
174 self.data = delete_from(&self.data, selection)?;
175 self.coordinates_changed = true;
176 self.requires_full_validation = true;
177 Ok(())
178 }
179
180 pub fn commit(mut self) -> Result<Structure, Vec<Diagnostic>> {
187 if self.coordinates_changed || self.requires_full_validation {
188 let Some(generation) = self.data.generation.next() else {
189 return Err(vec![
190 Diagnostic::new(Code::E6003).with_context("coordinate_generation", "exhausted"),
191 ]);
192 };
193 self.data.generation = generation;
194 }
195 let findings = if self.requires_full_validation {
196 validate(&self.data)
197 } else {
198 validate_coordinate_edit(&self.data)
199 };
200 if findings.is_empty() {
201 Ok(Structure::new(self.data))
202 } else {
203 Err(findings)
204 }
205 }
206}
207
208#[cfg(test)]
209#[path = "overlay_tests.rs"]
210mod tests;