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