1use threecrate_core::{PointCloud, TriangleMesh, Result, Point3f};
8use std::path::Path;
9use std::collections::HashMap;
10
11pub trait PointCloudReader: Send + Sync {
13 fn read_point_cloud(&self, path: &Path) -> Result<PointCloud<Point3f>>;
15
16 fn can_read(&self, path: &Path) -> bool;
18
19 fn format_name(&self) -> &'static str;
21}
22
23pub trait PointCloudWriter: Send + Sync {
25 fn write_point_cloud(&self, cloud: &PointCloud<Point3f>, path: &Path) -> Result<()>;
27
28 fn format_name(&self) -> &'static str;
30}
31
32pub trait MeshReader: Send + Sync {
34 fn read_mesh(&self, path: &Path) -> Result<TriangleMesh>;
36
37 fn can_read(&self, path: &Path) -> bool;
39
40 fn format_name(&self) -> &'static str;
42}
43
44pub trait MeshWriter: Send + Sync {
46 fn write_mesh(&self, mesh: &TriangleMesh, path: &Path) -> Result<()>;
48
49 fn format_name(&self) -> &'static str;
51}
52
53pub trait FormatHandler: PointCloudReader + PointCloudWriter + MeshReader + MeshWriter + Send + Sync {
55 fn supported_extensions(&self) -> &[&'static str];
57
58 fn magic_bytes(&self) -> &[u8];
60}
61
62pub struct IoRegistry {
64 point_cloud_readers: HashMap<String, Box<dyn PointCloudReader>>,
65 point_cloud_writers: HashMap<String, Box<dyn PointCloudWriter>>,
66 mesh_readers: HashMap<String, Box<dyn MeshReader>>,
67 mesh_writers: HashMap<String, Box<dyn MeshWriter>>,
68 #[allow(dead_code)] format_handlers: HashMap<String, Box<dyn FormatHandler>>,
70}
71
72impl IoRegistry {
73 pub fn new() -> Self {
75 Self {
76 point_cloud_readers: HashMap::new(),
77 point_cloud_writers: HashMap::new(),
78 mesh_readers: HashMap::new(),
79 mesh_writers: HashMap::new(),
80 format_handlers: HashMap::new(),
81 }
82 }
83
84 pub fn register_point_cloud_handler(&mut self, format: &str, handler: Box<dyn PointCloudReader>) {
86 self.point_cloud_readers.insert(format.to_lowercase(), handler);
87 }
88
89 pub fn register_point_cloud_writer(&mut self, format: &str, handler: Box<dyn PointCloudWriter>) {
91 self.point_cloud_writers.insert(format.to_lowercase(), handler);
92 }
93
94 pub fn register_mesh_handler(&mut self, format: &str, handler: Box<dyn MeshReader>) {
96 self.mesh_readers.insert(format.to_lowercase(), handler);
97 }
98
99 pub fn register_mesh_writer(&mut self, format: &str, handler: Box<dyn MeshWriter>) {
101 self.mesh_writers.insert(format.to_lowercase(), handler);
102 }
103
104 pub fn register_format_handler(&mut self, handler: Box<dyn FormatHandler>) {
106 let _handler = handler; }
111
112 pub fn read_point_cloud(&self, path: &Path, format_hint: &str) -> Result<PointCloud<Point3f>> {
114 if let Some(reader) = self.point_cloud_readers.get(&format_hint.to_lowercase()) {
116 if reader.can_read(path) {
117 return reader.read_point_cloud(path);
118 }
119 }
120
121 if let Some(detected_format) = self.detect_format_by_header(path) {
123 if let Some(reader) = self.point_cloud_readers.get(&detected_format) {
124 return reader.read_point_cloud(path);
125 }
126 }
127
128 if let Some(reader) = self.point_cloud_readers.get(&format_hint.to_lowercase()) {
130 return reader.read_point_cloud(path);
131 }
132
133 Err(threecrate_core::Error::UnsupportedFormat(
134 format!("No point cloud reader found for format: {}", format_hint)
135 ))
136 }
137
138 pub fn read_mesh(&self, path: &Path, format_hint: &str) -> Result<TriangleMesh> {
140 if let Some(reader) = self.mesh_readers.get(&format_hint.to_lowercase()) {
142 if reader.can_read(path) {
143 return reader.read_mesh(path);
144 }
145 }
146
147 if let Some(detected_format) = self.detect_format_by_header(path) {
149 if let Some(reader) = self.mesh_readers.get(&detected_format) {
150 return reader.read_mesh(path);
151 }
152 }
153
154 if let Some(reader) = self.mesh_readers.get(&format_hint.to_lowercase()) {
156 return reader.read_mesh(path);
157 }
158
159 Err(threecrate_core::Error::UnsupportedFormat(
160 format!("No mesh reader found for format: {}", format_hint)
161 ))
162 }
163
164 pub fn write_point_cloud(&self, cloud: &PointCloud<Point3f>, path: &Path, format_hint: &str) -> Result<()> {
166 if let Some(writer) = self.point_cloud_writers.get(&format_hint.to_lowercase()) {
167 return writer.write_point_cloud(cloud, path);
168 }
169
170 Err(threecrate_core::Error::UnsupportedFormat(
171 format!("No point cloud writer found for format: {}", format_hint)
172 ))
173 }
174
175 pub fn write_mesh(&self, mesh: &TriangleMesh, path: &Path, format_hint: &str) -> Result<()> {
177 if let Some(writer) = self.mesh_writers.get(&format_hint.to_lowercase()) {
178 return writer.write_mesh(mesh, path);
179 }
180
181 Err(threecrate_core::Error::UnsupportedFormat(
182 format!("No mesh writer found for format: {}", format_hint)
183 ))
184 }
185
186 fn detect_format_by_header(&self, path: &Path) -> Option<String> {
188 use std::fs::File;
189 use std::io::Read;
190
191 let mut file = match File::open(path) {
192 Ok(file) => file,
193 Err(_) => return None,
194 };
195
196 let mut header = [0u8; 16];
197 if let Ok(bytes_read) = file.read(&mut header) {
198 if bytes_read < 4 {
199 return None;
200 }
201
202 if header.starts_with(b"ply") {
204 return Some("ply".to_string());
205 } else if header.starts_with(b"#") || header.starts_with(b"v ") {
206 let mut file = File::open(path).ok()?;
208 let mut content = String::new();
209 if file.read_to_string(&mut content).is_ok() {
210 if content.lines().any(|line| line.trim().starts_with("v ")) {
211 return Some("obj".to_string());
212 }
213 }
214 } else if header.starts_with(b"LASF") {
215 return Some("las".to_string());
216 } else if header.starts_with(b"# .PCD") {
217 return Some("pcd".to_string());
218 }
219 }
220
221 None
222 }
223
224 pub fn supported_point_cloud_formats(&self) -> Vec<String> {
226 self.point_cloud_readers.keys().cloned().collect()
227 }
228
229 pub fn supported_mesh_formats(&self) -> Vec<String> {
231 self.mesh_readers.keys().cloned().collect()
232 }
233
234 pub fn supports_point_cloud_reading(&self, format: &str) -> bool {
236 self.point_cloud_readers.contains_key(&format.to_lowercase())
237 }
238
239 pub fn supports_point_cloud_writing(&self, format: &str) -> bool {
241 self.point_cloud_writers.contains_key(&format.to_lowercase())
242 }
243
244 pub fn supports_mesh_reading(&self, format: &str) -> bool {
246 self.mesh_readers.contains_key(&format.to_lowercase())
247 }
248
249 pub fn supports_mesh_writing(&self, format: &str) -> bool {
251 self.mesh_writers.contains_key(&format.to_lowercase())
252 }
253}
254
255impl Default for IoRegistry {
256 fn default() -> Self {
257 Self::new()
258 }
259}
260
261#[cfg(test)]
265mod tests {
266 use super::*;
267 use threecrate_core::Point3f;
268 use std::fs;
269
270 struct MockPlyHandler;
272
273 impl PointCloudReader for MockPlyHandler {
274 fn read_point_cloud(&self, _path: &Path) -> Result<PointCloud<Point3f>> {
275 let mut cloud = PointCloud::new();
276 cloud.push(Point3f::new(0.0, 0.0, 0.0));
277 Ok(cloud)
278 }
279
280 fn can_read(&self, _path: &Path) -> bool {
281 true
282 }
283
284 fn format_name(&self) -> &'static str {
285 "ply"
286 }
287 }
288
289 impl PointCloudWriter for MockPlyHandler {
290 fn write_point_cloud(&self, _cloud: &PointCloud<Point3f>, _path: &Path) -> Result<()> {
291 Ok(())
292 }
293
294 fn format_name(&self) -> &'static str {
295 "ply"
296 }
297 }
298
299 impl MeshReader for MockPlyHandler {
300 fn read_mesh(&self, _path: &Path) -> Result<TriangleMesh> {
301 let vertices = vec![Point3f::new(0.0, 0.0, 0.0)];
302 let faces = vec![];
303 Ok(TriangleMesh::from_vertices_and_faces(vertices, faces))
304 }
305
306 fn can_read(&self, _path: &Path) -> bool {
307 true
308 }
309
310 fn format_name(&self) -> &'static str {
311 "ply"
312 }
313 }
314
315 impl MeshWriter for MockPlyHandler {
316 fn write_mesh(&self, _mesh: &TriangleMesh, _path: &Path) -> Result<()> {
317 Ok(())
318 }
319
320 fn format_name(&self) -> &'static str {
321 "ply"
322 }
323 }
324
325 impl FormatHandler for MockPlyHandler {
326 fn supported_extensions(&self) -> &[&'static str] {
327 &["ply"]
328 }
329
330 fn magic_bytes(&self) -> &[u8] {
331 b"ply"
332 }
333 }
334
335 impl Clone for MockPlyHandler {
336 fn clone(&self) -> Self {
337 Self
338 }
339 }
340
341 #[test]
342 fn test_registry_registration() {
343 let mut registry = IoRegistry::new();
344
345 registry.register_point_cloud_handler("ply", Box::new(MockPlyHandler));
347 registry.register_mesh_handler("ply", Box::new(MockPlyHandler));
348 registry.register_point_cloud_writer("ply", Box::new(MockPlyHandler));
349 registry.register_mesh_writer("ply", Box::new(MockPlyHandler));
350
351 assert!(registry.supports_point_cloud_reading("ply"));
353 assert!(registry.supports_mesh_reading("ply"));
354 assert!(registry.supports_point_cloud_writing("ply"));
355 assert!(registry.supports_mesh_writing("ply"));
356
357 assert!(!registry.supports_point_cloud_reading("obj"));
359 assert!(!registry.supports_mesh_reading("xyz"));
360 }
361
362 #[test]
363 fn test_format_detection() {
364 let mut registry = IoRegistry::new();
365 registry.register_point_cloud_handler("ply", Box::new(MockPlyHandler));
366 registry.register_mesh_handler("ply", Box::new(MockPlyHandler));
367
368 let temp_file = "test_detection.ply";
370 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";
371 fs::write(temp_file, ply_content).unwrap();
372
373 let cloud = registry.read_point_cloud(Path::new(temp_file), "ply").unwrap();
375 assert_eq!(cloud.len(), 1);
376
377 let mesh = registry.read_mesh(Path::new(temp_file), "ply").unwrap();
378 assert_eq!(mesh.vertex_count(), 1);
379
380 let _ = fs::remove_file(temp_file);
382 }
383
384 #[test]
385 fn test_unsupported_format() {
386 let registry = IoRegistry::new();
387
388 let result = registry.read_point_cloud(Path::new("test.xyz"), "xyz");
389 assert!(result.is_err());
390
391 let result = registry.read_mesh(Path::new("test.xyz"), "xyz");
392 assert!(result.is_err());
393 }
394
395 #[test]
396 fn test_supported_formats_list() {
397 let mut registry = IoRegistry::new();
398 registry.register_point_cloud_handler("ply", Box::new(MockPlyHandler));
399 registry.register_mesh_handler("obj", Box::new(MockPlyHandler));
400
401 let pc_formats = registry.supported_point_cloud_formats();
402 let mesh_formats = registry.supported_mesh_formats();
403
404 assert!(pc_formats.contains(&"ply".to_string()));
405 assert!(mesh_formats.contains(&"obj".to_string()));
406 }
407}