Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ js-sys = "0.3"
[features]
webgpu = ["bevy/webgpu"]
webgl2 = ["bevy/webgl2"]
dev-track-location = ["bevy/track_location"]

[lib]
# need both, cdylib for wasm, rlib for main.rs
Expand Down
14 changes: 14 additions & 0 deletions assets/c6h6.xyz
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
12
Benzene C6H6
C 0.000000 1.396792 0.000000
C 1.209657 0.698396 0.000000
C 1.209657 -0.698396 0.000000
C 0.000000 -1.396792 0.000000
C -1.209657 -0.698396 0.000000
C -1.209657 0.698396 0.000000
H 0.000000 2.490291 0.000000
H 2.156659 1.245145 0.000000
H 2.156659 -1.245145 0.000000
H 0.000000 -2.490291 0.000000
H -2.156659 -1.245145 0.000000
H -2.156659 1.245145 0.000000
96 changes: 92 additions & 4 deletions src/io.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
// io.rs
use bevy::prelude::*;
use std::path::PathBuf;

use crate::parse::parse_xyz_content;
use crate::structure::{Atom, Crystal};

// System to load crystal data
pub fn load_crystal(mut commands: Commands) {
// For now, use the default water molecule structure
// In the future, this can be extended to load from embedded assets or user input
// System to load default crystal data
pub(crate) fn load_default_crystal(mut commands: Commands) {
println!("Loading default water molecule structure");

let crystal = Crystal {
Expand Down Expand Up @@ -33,3 +34,90 @@ pub fn load_crystal(mut commands: Commands) {

commands.insert_resource(crystal);
}

// Resource to handle file drag and drop
#[derive(Resource, Default)]
pub(crate) struct FileDragDrop {
dragged_file: Option<PathBuf>,
loaded_crystal: Option<Crystal>,
}

impl FileDragDrop {
pub(crate) fn dragged_file(&self) -> Option<&PathBuf> {
self.dragged_file.as_ref()
}
}

// System to handle file drag and drop events
pub(crate) fn handle_file_drag_drop(
mut drag_drop_events: EventReader<bevy::window::FileDragAndDrop>,
mut file_drag_drop: ResMut<FileDragDrop>,
) {
for event in drag_drop_events.read() {
match event {
bevy::window::FileDragAndDrop::DroppedFile { path_buf, .. } => {
println!("File dropped: {:?}", path_buf);

if let Some(extension) = path_buf.extension() {
if extension == "xyz" {
file_drag_drop.dragged_file = Some(path_buf.clone());
} else {
println!("Unsupported file type. Please drop an XYZ file.");
}
}
}
bevy::window::FileDragAndDrop::HoveredFile { path_buf, .. } => {
println!("File hovered: {:?}", path_buf);
}
bevy::window::FileDragAndDrop::HoveredFileCanceled { .. } => {
println!("File hover canceled");
}
}
}
}

// System to load crystal from dropped file
pub(crate) fn load_dropped_file(
mut file_drag_drop: ResMut<FileDragDrop>,
mut crystal_loaded: Local<bool>,
) {
if let Some(ref path) = file_drag_drop.dragged_file {
if !*crystal_loaded {
match std::fs::read_to_string(path) {
Ok(contents) => match parse_xyz_content(&contents) {
Ok(crystal) => {
println!("Successfully loaded crystal from: {:?}", path);
file_drag_drop.loaded_crystal = Some(crystal);
*crystal_loaded = true;
}
Err(e) => {
eprintln!("Failed to parse XYZ file: {}", e);
}
},
Err(e) => {
eprintln!("Failed to read file: {}", e);
}
}
}
}
}

// System to update crystal resource when new file is loaded
pub(crate) fn update_crystal_from_file(
mut commands: Commands,
file_drag_drop: Res<FileDragDrop>,
current_crystal: Option<Res<Crystal>>,
) {
if let Some(crystal) = &file_drag_drop.loaded_crystal {
// Only update if this is a new crystal
if let Some(current) = current_crystal {
if current.atoms.len() != crystal.atoms.len() {
commands.insert_resource(crystal.clone());
println!("Crystal updated with {} atoms", crystal.atoms.len());
}
} else {
commands.insert_resource(crystal.clone());
println!("Crystal loaded with {} atoms", crystal.atoms.len());
}
}
}
22 changes: 15 additions & 7 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ pub(crate) mod parse;
pub(crate) mod structure;

use crate::client::{poll_websocket_stream, setup_websocket_stream};
use crate::io::load_crystal;
use crate::io::{handle_file_drag_drop, load_dropped_file, update_crystal_from_file, FileDragDrop};
use crate::structure::{update_crystal_system, UpdateStructure};
use crate::ui::reset_camera_button_interaction;
use crate::ui::{
camera_controls, refresh_atoms_system, setup_cameras, setup_scene, toggle_light_attachment,
camera_controls, handle_load_default_button, refresh_atoms_system,
reset_camera_button_interaction, setup_cameras, setup_file_ui, setup_light,
toggle_light_attachment, update_file_ui, update_scene,
};
use crate::ui::{setup_buttons, spawn_axis};

Expand All @@ -36,28 +37,35 @@ pub fn run_app() {
filter: "wgpu=error,bevy_render=info,bevy_ecs=trace".to_string(),
custom_layer: |_| None,
}))
.init_resource::<FileDragDrop>()
.add_event::<UpdateStructure>()
.add_systems(Startup, load_crystal)
.add_systems(Startup, setup_scene.after(load_crystal))
.add_event::<bevy::window::FileDragAndDrop>()
.add_systems(
Startup,
(
setup_cameras,
spawn_axis,
setup_buttons,
setup_file_ui,
setup_websocket_stream,
)
.after(setup_scene),
),
)
.add_systems(Startup, (setup_light).after(setup_cameras))
.add_systems(
Update,
(
poll_websocket_stream,
update_crystal_system,
handle_file_drag_drop,
load_dropped_file,
update_crystal_from_file,
update_file_ui,
refresh_atoms_system,
toggle_light_attachment,
reset_camera_button_interaction,
handle_load_default_button,
camera_controls,
update_scene,
),
)
.run();
Expand Down
10 changes: 6 additions & 4 deletions src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ use crate::structure::{Atom, Crystal};
use anyhow::{Context, Result};

// Function to parse XYZ file format from string content
#[allow(dead_code)]
fn parse_xyz_content(contents: &str) -> Result<Crystal> {
pub(crate) fn parse_xyz_content(contents: &str) -> Result<Crystal> {
let lines = contents.lines().collect::<Vec<&str>>();

if lines.len() < 2 {
Expand All @@ -16,8 +15,11 @@ fn parse_xyz_content(contents: &str) -> Result<Crystal> {
.parse()
.context("Failed to parse number of atoms")?;

// Second line is a comment (we can skip it)
// Remaining lines contain atom data
// Second line may contain comment or extended XYZ properties
let _comment_line = lines[1].trim();

// Parse extended XYZ properties if present (basic implementation)
// For now, we'll focus on the basic XYZ format

let mut atoms = Vec::new();

Expand Down
10 changes: 6 additions & 4 deletions src/structure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ pub struct Atom {
}

// Structure to hold our crystal data
#[derive(Resource)]
#[derive(Resource, Clone)]
pub struct Crystal {
pub atoms: Vec<Atom>,
}
Expand All @@ -31,10 +31,12 @@ pub struct UpdateStructure {

// System to handle incoming structure updates
pub fn update_crystal_system(
mut crystal: ResMut<Crystal>,
crystal: Option<ResMut<Crystal>>,
mut events: EventReader<UpdateStructure>,
) {
for event in events.read() {
crystal.atoms = event.atoms.clone();
if let Some(mut crystal) = crystal {
for event in events.read() {
crystal.atoms.clone_from(&event.atoms);
}
}
}
Loading