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