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 } else if header.starts_with(b"ASTM-E57") {
219 return Some("e57".to_string());
220 }
221 }
222
223 None
224 }
225
226 pub fn supported_point_cloud_formats(&self) -> Vec<String> {
228 self.point_cloud_readers.keys().cloned().collect()
229 }
230
231 pub fn supported_mesh_formats(&self) -> Vec<String> {
233 self.mesh_readers.keys().cloned().collect()
234 }
235
236 pub fn supports_point_cloud_reading(&self, format: &str) -> bool {
238 self.point_cloud_readers.contains_key(&format.to_lowercase())
239 }
240
241 pub fn supports_point_cloud_writing(&self, format: &str) -> bool {
243 self.point_cloud_writers.contains_key(&format.to_lowercase())
244 }
245
246 pub fn supports_mesh_reading(&self, format: &str) -> bool {
248 self.mesh_readers.contains_key(&format.to_lowercase())
249 }
250
251 pub fn supports_mesh_writing(&self, format: &str) -> bool {
253 self.mesh_writers.contains_key(&format.to_lowercase())
254 }
255}
256
257impl Default for IoRegistry {
258 fn default() -> Self {
259 Self::new()
260 }
261}
262
263#[cfg(test)]
267mod tests {
268 use super::*;
269 use threecrate_core::Point3f;
270 use std::fs;
271
272 struct MockPlyHandler;
274
275 impl PointCloudReader for MockPlyHandler {
276 fn read_point_cloud(&self, _path: &Path) -> Result<PointCloud<Point3f>> {
277 let mut cloud = PointCloud::new();
278 cloud.push(Point3f::new(0.0, 0.0, 0.0));
279 Ok(cloud)
280 }
281
282 fn can_read(&self, _path: &Path) -> bool {
283 true
284 }
285
286 fn format_name(&self) -> &'static str {
287 "ply"
288 }
289 }
290
291 impl PointCloudWriter for MockPlyHandler {
292 fn write_point_cloud(&self, _cloud: &PointCloud<Point3f>, _path: &Path) -> Result<()> {
293 Ok(())
294 }
295
296 fn format_name(&self) -> &'static str {
297 "ply"
298 }
299 }
300
301 impl MeshReader for MockPlyHandler {
302 fn read_mesh(&self, _path: &Path) -> Result<TriangleMesh> {
303 let vertices = vec![Point3f::new(0.0, 0.0, 0.0)];
304 let faces = vec![];
305 Ok(TriangleMesh::from_vertices_and_faces(vertices, faces))
306 }
307
308 fn can_read(&self, _path: &Path) -> bool {
309 true
310 }
311
312 fn format_name(&self) -> &'static str {
313 "ply"
314 }
315 }
316
317 impl MeshWriter for MockPlyHandler {
318 fn write_mesh(&self, _mesh: &TriangleMesh, _path: &Path) -> Result<()> {
319 Ok(())
320 }
321
322 fn format_name(&self) -> &'static str {
323 "ply"
324 }
325 }
326
327 impl FormatHandler for MockPlyHandler {
328 fn supported_extensions(&self) -> &[&'static str] {
329 &["ply"]
330 }
331
332 fn magic_bytes(&self) -> &[u8] {
333 b"ply"
334 }
335 }
336
337 impl Clone for MockPlyHandler {
338 fn clone(&self) -> Self {
339 Self
340 }
341 }
342
343 #[test]
344 fn test_registry_registration() {
345 let mut registry = IoRegistry::new();
346
347 registry.register_point_cloud_handler("ply", Box::new(MockPlyHandler));
349 registry.register_mesh_handler("ply", Box::new(MockPlyHandler));
350 registry.register_point_cloud_writer("ply", Box::new(MockPlyHandler));
351 registry.register_mesh_writer("ply", Box::new(MockPlyHandler));
352
353 assert!(registry.supports_point_cloud_reading("ply"));
355 assert!(registry.supports_mesh_reading("ply"));
356 assert!(registry.supports_point_cloud_writing("ply"));
357 assert!(registry.supports_mesh_writing("ply"));
358
359 assert!(!registry.supports_point_cloud_reading("obj"));
361 assert!(!registry.supports_mesh_reading("xyz"));
362 }
363
364 #[test]
365 fn test_format_detection() {
366 let mut registry = IoRegistry::new();
367 registry.register_point_cloud_handler("ply", Box::new(MockPlyHandler));
368 registry.register_mesh_handler("ply", Box::new(MockPlyHandler));
369
370 let temp_file = "test_detection.ply";
372 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";
373 fs::write(temp_file, ply_content).unwrap();
374
375 let cloud = registry.read_point_cloud(Path::new(temp_file), "ply").unwrap();
377 assert_eq!(cloud.len(), 1);
378
379 let mesh = registry.read_mesh(Path::new(temp_file), "ply").unwrap();
380 assert_eq!(mesh.vertex_count(), 1);
381
382 let _ = fs::remove_file(temp_file);
384 }
385
386 #[test]
387 fn test_unsupported_format() {
388 let registry = IoRegistry::new();
389
390 let result = registry.read_point_cloud(Path::new("test.xyz"), "xyz");
391 assert!(result.is_err());
392
393 let result = registry.read_mesh(Path::new("test.xyz"), "xyz");
394 assert!(result.is_err());
395 }
396
397 #[test]
398 fn test_supported_formats_list() {
399 let mut registry = IoRegistry::new();
400 registry.register_point_cloud_handler("ply", Box::new(MockPlyHandler));
401 registry.register_mesh_handler("obj", Box::new(MockPlyHandler));
402
403 let pc_formats = registry.supported_point_cloud_formats();
404 let mesh_formats = registry.supported_mesh_formats();
405
406 assert!(pc_formats.contains(&"ply".to_string()));
407 assert!(mesh_formats.contains(&"obj".to_string()));
408 }
409}