Skip to main content

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