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