use threecrate_core::{PointCloud, TriangleMesh, Result, Point3f};
use std::path::Path;
use std::collections::HashMap;
pub trait PointCloudReader: Send + Sync {
fn read_point_cloud(&self, path: &Path) -> Result<PointCloud<Point3f>>;
fn can_read(&self, path: &Path) -> bool;
fn format_name(&self) -> &'static str;
}
pub trait PointCloudWriter: Send + Sync {
fn write_point_cloud(&self, cloud: &PointCloud<Point3f>, path: &Path) -> Result<()>;
fn format_name(&self) -> &'static str;
}
pub trait MeshReader: Send + Sync {
fn read_mesh(&self, path: &Path) -> Result<TriangleMesh>;
fn can_read(&self, path: &Path) -> bool;
fn format_name(&self) -> &'static str;
}
pub trait MeshWriter: Send + Sync {
fn write_mesh(&self, mesh: &TriangleMesh, path: &Path) -> Result<()>;
fn format_name(&self) -> &'static str;
}
pub trait FormatHandler: PointCloudReader + PointCloudWriter + MeshReader + MeshWriter + Send + Sync {
fn supported_extensions(&self) -> &[&'static str];
fn magic_bytes(&self) -> &[u8];
}
pub struct IoRegistry {
point_cloud_readers: HashMap<String, Box<dyn PointCloudReader>>,
point_cloud_writers: HashMap<String, Box<dyn PointCloudWriter>>,
mesh_readers: HashMap<String, Box<dyn MeshReader>>,
mesh_writers: HashMap<String, Box<dyn MeshWriter>>,
#[allow(dead_code)] format_handlers: HashMap<String, Box<dyn FormatHandler>>,
}
impl IoRegistry {
pub fn new() -> Self {
Self {
point_cloud_readers: HashMap::new(),
point_cloud_writers: HashMap::new(),
mesh_readers: HashMap::new(),
mesh_writers: HashMap::new(),
format_handlers: HashMap::new(),
}
}
pub fn register_point_cloud_handler(&mut self, format: &str, handler: Box<dyn PointCloudReader>) {
self.point_cloud_readers.insert(format.to_lowercase(), handler);
}
pub fn register_point_cloud_writer(&mut self, format: &str, handler: Box<dyn PointCloudWriter>) {
self.point_cloud_writers.insert(format.to_lowercase(), handler);
}
pub fn register_mesh_handler(&mut self, format: &str, handler: Box<dyn MeshReader>) {
self.mesh_readers.insert(format.to_lowercase(), handler);
}
pub fn register_mesh_writer(&mut self, format: &str, handler: Box<dyn MeshWriter>) {
self.mesh_writers.insert(format.to_lowercase(), handler);
}
pub fn register_format_handler(&mut self, handler: Box<dyn FormatHandler>) {
let _handler = handler; }
pub fn read_point_cloud(&self, path: &Path, format_hint: &str) -> Result<PointCloud<Point3f>> {
if let Some(reader) = self.point_cloud_readers.get(&format_hint.to_lowercase()) {
if reader.can_read(path) {
return reader.read_point_cloud(path);
}
}
if let Some(detected_format) = self.detect_format_by_header(path) {
if let Some(reader) = self.point_cloud_readers.get(&detected_format) {
return reader.read_point_cloud(path);
}
}
if let Some(reader) = self.point_cloud_readers.get(&format_hint.to_lowercase()) {
return reader.read_point_cloud(path);
}
Err(threecrate_core::Error::UnsupportedFormat(
format!("No point cloud reader found for format: {}", format_hint)
))
}
pub fn read_mesh(&self, path: &Path, format_hint: &str) -> Result<TriangleMesh> {
if let Some(reader) = self.mesh_readers.get(&format_hint.to_lowercase()) {
if reader.can_read(path) {
return reader.read_mesh(path);
}
}
if let Some(detected_format) = self.detect_format_by_header(path) {
if let Some(reader) = self.mesh_readers.get(&detected_format) {
return reader.read_mesh(path);
}
}
if let Some(reader) = self.mesh_readers.get(&format_hint.to_lowercase()) {
return reader.read_mesh(path);
}
Err(threecrate_core::Error::UnsupportedFormat(
format!("No mesh reader found for format: {}", format_hint)
))
}
pub fn write_point_cloud(&self, cloud: &PointCloud<Point3f>, path: &Path, format_hint: &str) -> Result<()> {
if let Some(writer) = self.point_cloud_writers.get(&format_hint.to_lowercase()) {
return writer.write_point_cloud(cloud, path);
}
Err(threecrate_core::Error::UnsupportedFormat(
format!("No point cloud writer found for format: {}", format_hint)
))
}
pub fn write_mesh(&self, mesh: &TriangleMesh, path: &Path, format_hint: &str) -> Result<()> {
if let Some(writer) = self.mesh_writers.get(&format_hint.to_lowercase()) {
return writer.write_mesh(mesh, path);
}
Err(threecrate_core::Error::UnsupportedFormat(
format!("No mesh writer found for format: {}", format_hint)
))
}
fn detect_format_by_header(&self, path: &Path) -> Option<String> {
use std::fs::File;
use std::io::Read;
let mut file = match File::open(path) {
Ok(file) => file,
Err(_) => return None,
};
let mut header = [0u8; 16];
if let Ok(bytes_read) = file.read(&mut header) {
if bytes_read < 4 {
return None;
}
if header.starts_with(b"ply") {
return Some("ply".to_string());
} else if header.starts_with(b"#") || header.starts_with(b"v ") {
let mut file = File::open(path).ok()?;
let mut content = String::new();
if file.read_to_string(&mut content).is_ok() {
if content.lines().any(|line| line.trim().starts_with("v ")) {
return Some("obj".to_string());
}
}
} else if header.starts_with(b"LASF") {
return Some("las".to_string());
} else if header.starts_with(b"# .PCD") {
return Some("pcd".to_string());
} else if header.starts_with(b"ASTM-E57") {
return Some("e57".to_string());
}
}
None
}
pub fn supported_point_cloud_formats(&self) -> Vec<String> {
self.point_cloud_readers.keys().cloned().collect()
}
pub fn supported_mesh_formats(&self) -> Vec<String> {
self.mesh_readers.keys().cloned().collect()
}
pub fn supports_point_cloud_reading(&self, format: &str) -> bool {
self.point_cloud_readers.contains_key(&format.to_lowercase())
}
pub fn supports_point_cloud_writing(&self, format: &str) -> bool {
self.point_cloud_writers.contains_key(&format.to_lowercase())
}
pub fn supports_mesh_reading(&self, format: &str) -> bool {
self.mesh_readers.contains_key(&format.to_lowercase())
}
pub fn supports_mesh_writing(&self, format: &str) -> bool {
self.mesh_writers.contains_key(&format.to_lowercase())
}
}
impl Default for IoRegistry {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use threecrate_core::Point3f;
use std::fs;
struct MockPlyHandler;
impl PointCloudReader for MockPlyHandler {
fn read_point_cloud(&self, _path: &Path) -> Result<PointCloud<Point3f>> {
let mut cloud = PointCloud::new();
cloud.push(Point3f::new(0.0, 0.0, 0.0));
Ok(cloud)
}
fn can_read(&self, _path: &Path) -> bool {
true
}
fn format_name(&self) -> &'static str {
"ply"
}
}
impl PointCloudWriter for MockPlyHandler {
fn write_point_cloud(&self, _cloud: &PointCloud<Point3f>, _path: &Path) -> Result<()> {
Ok(())
}
fn format_name(&self) -> &'static str {
"ply"
}
}
impl MeshReader for MockPlyHandler {
fn read_mesh(&self, _path: &Path) -> Result<TriangleMesh> {
let vertices = vec![Point3f::new(0.0, 0.0, 0.0)];
let faces = vec![];
Ok(TriangleMesh::from_vertices_and_faces(vertices, faces))
}
fn can_read(&self, _path: &Path) -> bool {
true
}
fn format_name(&self) -> &'static str {
"ply"
}
}
impl MeshWriter for MockPlyHandler {
fn write_mesh(&self, _mesh: &TriangleMesh, _path: &Path) -> Result<()> {
Ok(())
}
fn format_name(&self) -> &'static str {
"ply"
}
}
impl FormatHandler for MockPlyHandler {
fn supported_extensions(&self) -> &[&'static str] {
&["ply"]
}
fn magic_bytes(&self) -> &[u8] {
b"ply"
}
}
impl Clone for MockPlyHandler {
fn clone(&self) -> Self {
Self
}
}
#[test]
fn test_registry_registration() {
let mut registry = IoRegistry::new();
registry.register_point_cloud_handler("ply", Box::new(MockPlyHandler));
registry.register_mesh_handler("ply", Box::new(MockPlyHandler));
registry.register_point_cloud_writer("ply", Box::new(MockPlyHandler));
registry.register_mesh_writer("ply", Box::new(MockPlyHandler));
assert!(registry.supports_point_cloud_reading("ply"));
assert!(registry.supports_mesh_reading("ply"));
assert!(registry.supports_point_cloud_writing("ply"));
assert!(registry.supports_mesh_writing("ply"));
assert!(!registry.supports_point_cloud_reading("obj"));
assert!(!registry.supports_mesh_reading("xyz"));
}
#[test]
fn test_format_detection() {
let mut registry = IoRegistry::new();
registry.register_point_cloud_handler("ply", Box::new(MockPlyHandler));
registry.register_mesh_handler("ply", Box::new(MockPlyHandler));
let temp_file = "test_detection.ply";
let ply_content = "ply\nformat ascii 1.0\nelement vertex 1\nproperty float x\nproperty float y\nproperty float z\nend_header\n0.0 0.0 0.0\n";
fs::write(temp_file, ply_content).unwrap();
let cloud = registry.read_point_cloud(Path::new(temp_file), "ply").unwrap();
assert_eq!(cloud.len(), 1);
let mesh = registry.read_mesh(Path::new(temp_file), "ply").unwrap();
assert_eq!(mesh.vertex_count(), 1);
let _ = fs::remove_file(temp_file);
}
#[test]
fn test_unsupported_format() {
let registry = IoRegistry::new();
let result = registry.read_point_cloud(Path::new("test.xyz"), "xyz");
assert!(result.is_err());
let result = registry.read_mesh(Path::new("test.xyz"), "xyz");
assert!(result.is_err());
}
#[test]
fn test_supported_formats_list() {
let mut registry = IoRegistry::new();
registry.register_point_cloud_handler("ply", Box::new(MockPlyHandler));
registry.register_mesh_handler("obj", Box::new(MockPlyHandler));
let pc_formats = registry.supported_point_cloud_formats();
let mesh_formats = registry.supported_mesh_formats();
assert!(pc_formats.contains(&"ply".to_string()));
assert!(mesh_formats.contains(&"obj".to_string()));
}
}