threecrate_io/
lib.rs

1//! I/O operations for point clouds and meshes
2//! 
3//! This crate provides functionality to read and write various 3D file formats
4//! including PLY, OBJ, and other common point cloud and mesh formats.
5
6pub mod ply;
7pub mod obj;
8#[cfg(feature = "las_laz")]
9pub mod pasture;
10pub mod pcd;
11pub mod xyz_csv;
12pub mod error;
13pub mod registry;
14pub mod mesh_attributes;
15pub mod serialization;
16#[cfg(feature = "io-mmap")]
17pub mod mmap;
18
19#[cfg(test)]
20pub mod tests;
21
22pub use error::*;
23pub use ply::{RobustPlyReader, RobustPlyWriter, PlyWriteOptions, PlyFormat, PlyValue};
24pub use obj::{RobustObjReader, RobustObjWriter, ObjData, ObjWriteOptions, Material, FaceVertex, Face, Group};
25pub use pcd::{RobustPcdReader, RobustPcdWriter, PcdWriteOptions, PcdDataFormat, PcdFieldType, PcdHeader, PcdValue};
26pub use xyz_csv::{XyzCsvReader, XyzCsvWriter, XyzCsvStreamingReader, XyzCsvWriteOptions, XyzCsvSchema, XyzCsvPoint, Delimiter, ColumnType};
27pub use registry::{IoRegistry, FormatHandler};
28pub use mesh_attributes::{ExtendedTriangleMesh, MeshAttributeOptions, MeshMetadata, Tangent, UV};
29pub use serialization::{SerializationOptions, AttributePreservingReader, AttributePreservingWriter};
30
31use threecrate_core::{PointCloud, TriangleMesh, Result, Point3f};
32use std::path::Path;
33
34// Legacy traits for backward compatibility
35/// Trait for reading point clouds from files
36pub trait PointCloudReader {
37    fn read_point_cloud<P: AsRef<std::path::Path>>(path: P) -> Result<PointCloud<Point3f>>;
38}
39
40/// Trait for writing point clouds to files
41pub trait PointCloudWriter {
42    fn write_point_cloud<P: AsRef<std::path::Path>>(cloud: &PointCloud<Point3f>, path: P) -> Result<()>;
43}
44
45/// Trait for reading meshes from files
46pub trait MeshReader {
47    fn read_mesh<P: AsRef<std::path::Path>>(path: P) -> Result<TriangleMesh>;
48}
49
50/// Trait for writing meshes to files
51pub trait MeshWriter {
52    fn write_mesh<P: AsRef<std::path::Path>>(mesh: &TriangleMesh, path: P) -> Result<()>;
53}
54
55// Global IO registry instance
56lazy_static::lazy_static! {
57    static ref IO_REGISTRY: IoRegistry = {
58        let mut registry = IoRegistry::new();
59        
60        // Register PLY format handlers
61        registry.register_point_cloud_handler("ply", Box::new(ply::PlyReader));
62        registry.register_mesh_handler("ply", Box::new(ply::PlyReader));
63        registry.register_point_cloud_writer("ply", Box::new(ply::PlyWriter));
64        registry.register_mesh_writer("ply", Box::new(ply::PlyWriter));
65        
66        // Register OBJ format handlers
67        registry.register_mesh_handler("obj", Box::new(obj::ObjReader));
68        registry.register_mesh_writer("obj", Box::new(obj::ObjWriter));
69        
70        // Register pasture format handlers (when feature is enabled)
71        #[cfg(feature = "las_laz")]
72        {
73            registry.register_point_cloud_handler("las", Box::new(pasture::PastureReader));
74            registry.register_point_cloud_handler("laz", Box::new(pasture::PastureReader));
75            registry.register_point_cloud_writer("las", Box::new(pasture::PastureWriter));
76            registry.register_point_cloud_writer("laz", Box::new(pasture::PastureWriter));
77        }
78        registry.register_point_cloud_handler("pcd", Box::new(pcd::PcdReader));
79        registry.register_point_cloud_writer("pcd", Box::new(pcd::PcdWriter));
80        
81        // Register XYZ/CSV format handlers
82        registry.register_point_cloud_handler("xyz", Box::new(xyz_csv::XyzCsvReader));
83        registry.register_point_cloud_handler("csv", Box::new(xyz_csv::XyzCsvReader));
84        registry.register_point_cloud_handler("txt", Box::new(xyz_csv::XyzCsvReader));
85        registry.register_point_cloud_writer("xyz", Box::new(xyz_csv::XyzCsvWriter));
86        registry.register_point_cloud_writer("csv", Box::new(xyz_csv::XyzCsvWriter));
87        registry.register_point_cloud_writer("txt", Box::new(xyz_csv::XyzCsvWriter));
88        
89        registry
90    };
91}
92
93/// Auto-detect format and read point cloud using the unified registry
94pub fn read_point_cloud<P: AsRef<Path>>(path: P) -> Result<PointCloud<Point3f>> {
95    let path = path.as_ref();
96    let extension = path.extension()
97        .and_then(|s| s.to_str())
98        .ok_or_else(|| threecrate_core::Error::UnsupportedFormat(
99            "No file extension found".to_string()
100        ))?;
101    
102    IO_REGISTRY.read_point_cloud(path, extension)
103}
104
105/// Auto-detect format and read mesh using the unified registry
106pub fn read_mesh<P: AsRef<Path>>(path: P) -> Result<TriangleMesh> {
107    let path = path.as_ref();
108    let extension = path.extension()
109        .and_then(|s| s.to_str())
110        .ok_or_else(|| threecrate_core::Error::UnsupportedFormat(
111            "No file extension found".to_string()
112        ))?;
113    
114    IO_REGISTRY.read_mesh(path, extension)
115}
116
117/// Write point cloud with format auto-detection using the unified registry
118pub fn write_point_cloud<P: AsRef<Path>>(cloud: &PointCloud<Point3f>, path: P) -> Result<()> {
119    let path = path.as_ref();
120    let extension = path.extension()
121        .and_then(|s| s.to_str())
122        .ok_or_else(|| threecrate_core::Error::UnsupportedFormat(
123            "No file extension found".to_string()
124        ))?;
125    
126    IO_REGISTRY.write_point_cloud(cloud, path, extension)
127}
128
129/// Write mesh with format auto-detection using the unified registry
130pub fn write_mesh<P: AsRef<Path>>(mesh: &TriangleMesh, path: P) -> Result<()> {
131    let path = path.as_ref();
132    let extension = path.extension()
133        .and_then(|s| s.to_str())
134        .ok_or_else(|| threecrate_core::Error::UnsupportedFormat(
135            "No file extension found".to_string()
136        ))?;
137    
138    IO_REGISTRY.write_mesh(mesh, path, extension)
139}
140
141/// Get the global IO registry for advanced usage
142pub fn get_io_registry() -> &'static IoRegistry {
143    &IO_REGISTRY
144}
145
146/// Streaming point cloud reader for large files
147/// 
148/// This function returns an iterator that reads points one by one without loading
149/// the entire file into memory. Useful for processing very large point cloud files.
150/// 
151/// # Arguments
152/// * `path` - Path to the point cloud file
153/// * `chunk_size` - Optional chunk size for internal buffering (default: 1000)
154/// 
155/// # Returns
156/// An iterator over `Result<Point3f>` where each item is either a point or an error
157/// 
158/// # Example
159/// ```rust
160/// use threecrate_io::read_point_cloud_iter;
161/// 
162/// // Note: This will fail if the file doesn't exist, but demonstrates the API
163/// match read_point_cloud_iter("large_cloud.ply", Some(5000)) {
164///     Ok(iter) => {
165///         for result in iter {
166///             match result {
167///                 Ok(point) => println!("Point: {:?}", point),
168///                 Err(e) => eprintln!("Error: {}", e),
169///             }
170///         }
171///     }
172///     Err(e) => eprintln!("Failed to open file: {}", e),
173/// }
174/// # Ok::<(), Box<dyn std::error::Error>>(())
175/// ```
176pub fn read_point_cloud_iter<P: AsRef<Path>>(
177    path: P, 
178    chunk_size: Option<usize>
179) -> Result<Box<dyn Iterator<Item = Result<Point3f>> + Send + Sync>> {
180    let path = path.as_ref();
181    let extension = path.extension()
182        .and_then(|s| s.to_str())
183        .ok_or_else(|| threecrate_core::Error::UnsupportedFormat(
184            "No file extension found".to_string()
185        ))?;
186    
187    match extension {
188        "ply" => {
189            let iter = ply::PlyStreamingReader::new(path, chunk_size.unwrap_or(1000))?;
190            Ok(Box::new(iter))
191        }
192        "obj" => {
193            let iter = obj::ObjStreamingReader::new(path, chunk_size.unwrap_or(1000))?;
194            Ok(Box::new(iter))
195        }
196        "xyz" | "csv" | "txt" => {
197            let iter = xyz_csv::XyzCsvStreamingReader::new(path, chunk_size.unwrap_or(1000))?;
198            Ok(Box::new(iter))
199        }
200        _ => Err(threecrate_core::Error::UnsupportedFormat(
201            format!("Streaming not supported for format: {}", extension)
202        ))
203    }
204}
205
206/// Streaming mesh reader for large files
207/// 
208/// This function returns an iterator that reads mesh faces one by one without loading
209/// the entire file into memory. Useful for processing very large mesh files.
210/// 
211/// # Arguments
212/// * `path` - Path to the mesh file
213/// * `chunk_size` - Optional chunk size for internal buffering (default: 1000)
214/// 
215/// # Returns
216/// An iterator over `Result<[usize; 3]>` where each item is either a face or an error
217/// 
218/// # Example
219/// ```rust
220/// use threecrate_io::read_mesh_iter;
221/// 
222/// // Note: This will fail if the file doesn't exist, but demonstrates the API
223/// match read_mesh_iter("large_mesh.obj", Some(5000)) {
224///     Ok(iter) => {
225///         for result in iter {
226///             match result {
227///                 Ok(face) => println!("Face: {:?}", face),
228///                 Err(e) => eprintln!("Error: {}", e),
229///             }
230///         }
231///     }
232///     Err(e) => eprintln!("Failed to open file: {}", e),
233/// }
234/// # Ok::<(), Box<dyn std::error::Error>>(())
235/// ```
236pub fn read_mesh_iter<P: AsRef<Path>>(
237    path: P, 
238    chunk_size: Option<usize>
239) -> Result<Box<dyn Iterator<Item = Result<[usize; 3]>> + Send + Sync>> {
240    let path = path.as_ref();
241    let extension = path.extension()
242        .and_then(|s| s.to_str())
243        .ok_or_else(|| threecrate_core::Error::UnsupportedFormat(
244            "No file extension found".to_string()
245        ))?;
246    
247    match extension {
248        "ply" => {
249            let iter = ply::PlyMeshStreamingReader::new(path, chunk_size.unwrap_or(1000))?;
250            Ok(Box::new(iter))
251        }
252        "obj" => {
253            let iter = obj::ObjMeshStreamingReader::new(path, chunk_size.unwrap_or(1000))?;
254            Ok(Box::new(iter))
255        }
256        _ => Err(threecrate_core::Error::UnsupportedFormat(
257            format!("Streaming not supported for format: {}", extension)
258        ))
259    }
260}
261
262// Legacy tests moved to tests/ module
263