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