ferritin_structure_mesh/
plugin.rs

1//! Module for loading PDBs into Bevy via the Plugin system
2//!
3//! Over time this would be a good candidate for factoring out
4//!
5use super::{ColorScheme, RenderOptions, Structure};
6use bevy::prelude::*;
7use ferritin_core::{AtomCollection, load_structure};
8use std::path::Path;
9use std::path::PathBuf;
10
11#[derive(Clone)]
12pub struct StructureSettings {
13    pub render_type: RenderOptions,
14    pub color_scheme: ColorScheme,
15    pub material: StandardMaterial,
16}
17impl Default for StructureSettings {
18    fn default() -> Self {
19        Self {
20            render_type: RenderOptions::Solid,
21            color_scheme: ColorScheme::Solid(Color::WHITE),
22            material: StandardMaterial::default(),
23        }
24    }
25}
26
27// adding this for integration with Bevy
28pub struct StructurePlugin {
29    initial_files: Vec<(PathBuf, StructureSettings)>,
30}
31
32impl StructurePlugin {
33    pub fn new() -> Self {
34        Self {
35            initial_files: Vec::new(),
36        }
37    }
38    pub fn with_file<P: Into<PathBuf>>(
39        mut self,
40        path: P,
41        settings: Option<StructureSettings>,
42    ) -> Self {
43        self.initial_files
44            .push((path.into(), settings.unwrap_or_default()));
45        self
46    }
47}
48
49// adding this for integration with Bevy
50impl Plugin for StructurePlugin {
51    fn build(&self, app: &mut App) {
52        app.insert_resource(StructureFiles(self.initial_files.clone()))
53            .add_systems(Startup, load_initial_proteins)
54            .add_event::<LoadProteinEvent>();
55    }
56}
57
58#[derive(Resource)]
59struct StructureFiles(Vec<(PathBuf, StructureSettings)>);
60
61#[derive(Event)]
62pub struct LoadProteinEvent(pub PathBuf);
63
64fn load_initial_proteins(
65    structure_files: Res<StructureFiles>,
66    mut commands: Commands,
67    mut meshes: ResMut<Assets<Mesh>>,
68    mut materials: ResMut<Assets<StandardMaterial>>,
69) {
70    for (file_path, settings) in &structure_files.0 {
71        // check valid filepath
72        if !Path::new(file_path).exists() {
73            eprintln!("Error: File not found: {:?}", file_path);
74            continue;
75        }
76
77        // Todo: revisit this portion about the right default visuals later on
78        // by default lets only keep the amino acids.
79        let mut ac: AtomCollection = load_structure(file_path).unwrap();
80
81        // add the bonds back in as they are removed during the collection process above.
82        ac.connect_via_residue_names();
83
84        let structure = Structure::builder()
85            .pdb(ac)
86            .rendertype(settings.render_type.clone())
87            .color_scheme(settings.color_scheme.clone())
88            .material(settings.material.clone())
89            .build();
90
91        let mesh = structure.to_mesh();
92        let material = structure.get_material();
93
94        commands.spawn((
95            Mesh3d(meshes.add(mesh)),
96            MeshMaterial3d(materials.add(material)),
97        ));
98    }
99}