rs_pcd/header/
builder.rs

1// Copyright 2025 bigpear0201
2
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6
7//     http://www.apache.org/licenses/LICENSE-2.0
8
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Builder pattern for constructing PcdHeader.
16//! 
17//! This provides a more ergonomic API than manually constructing a PcdHeader,
18//! automatically deriving sizes, types, and counts from the ValueType.
19//! 
20//! # Example
21//! 
22//! ```rust
23//! use rs_pcd::header::{PcdHeaderBuilder, ValueType, DataFormat};
24//! 
25//! let header = PcdHeaderBuilder::new()
26//!     .add_field("x", ValueType::F32)
27//!     .add_field("y", ValueType::F32)
28//!     .add_field("z", ValueType::F32)
29//!     .add_field("intensity", ValueType::F32)
30//!     .width(1000)
31//!     .data_format(DataFormat::Binary)
32//!     .build()
33//!     .unwrap();
34//! ```
35
36use super::{DataFormat, PcdHeader, ValueType};
37use crate::error::{PcdError, Result};
38
39/// Builder for constructing PcdHeader with a fluent API.
40#[derive(Debug, Clone)]
41pub struct PcdHeaderBuilder {
42    fields: Vec<(String, ValueType)>,
43    width: Option<u32>,
44    height: u32,
45    data: DataFormat,
46    viewpoint: [f64; 7],
47    version: String,
48}
49
50impl Default for PcdHeaderBuilder {
51    fn default() -> Self {
52        Self::new()
53    }
54}
55
56impl PcdHeaderBuilder {
57    /// Create a new builder with default values.
58    pub fn new() -> Self {
59        Self {
60            fields: Vec::new(),
61            width: None,
62            height: 1,
63            data: DataFormat::Binary,
64            viewpoint: [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0],
65            version: "0.7".to_string(),
66        }
67    }
68
69    /// Add a field with the given name and type.
70    /// Fields are added in order and can only have count=1.
71    /// For fields with count > 1, use `add_field_with_count`.
72    #[must_use]
73    pub fn add_field(mut self, name: &str, value_type: ValueType) -> Self {
74        self.fields.push((name.to_string(), value_type));
75        self
76    }
77
78    /// Set the width (number of points per row).
79    /// For unorganized point clouds, this equals the total number of points.
80    #[must_use]
81    pub fn width(mut self, w: u32) -> Self {
82        self.width = Some(w);
83        self
84    }
85
86    /// Set the height (number of rows).
87    /// Default is 1 (unorganized point cloud).
88    #[must_use]
89    pub fn height(mut self, h: u32) -> Self {
90        self.height = h;
91        self
92    }
93
94    /// Set the data format (Ascii, Binary, or BinaryCompressed).
95    /// Default is Binary.
96    #[must_use]
97    pub fn data_format(mut self, fmt: DataFormat) -> Self {
98        self.data = fmt;
99        self
100    }
101
102    /// Set the viewpoint (tx, ty, tz, qw, qx, qy, qz).
103    /// Default is identity: [0, 0, 0, 1, 0, 0, 0].
104    #[must_use]
105    pub fn viewpoint(mut self, vp: [f64; 7]) -> Self {
106        self.viewpoint = vp;
107        self
108    }
109
110    /// Set the PCD version string.
111    /// Default is "0.7".
112    #[must_use]
113    pub fn version(mut self, v: &str) -> Self {
114        self.version = v.to_string();
115        self
116    }
117
118    /// Build the PcdHeader.
119    /// Returns an error if width is not set.
120    pub fn build(self) -> Result<PcdHeader> {
121        let width = self.width.ok_or_else(|| {
122            PcdError::InvalidHeader {
123                line: 0,
124                msg: "Width must be set".to_string(),
125            }
126        })?;
127
128        if self.fields.is_empty() {
129            return Err(PcdError::InvalidHeader {
130                line: 0,
131                msg: "At least one field must be added".to_string(),
132            });
133        }
134
135        let mut field_names = Vec::with_capacity(self.fields.len());
136        let mut sizes = Vec::with_capacity(self.fields.len());
137        let mut types = Vec::with_capacity(self.fields.len());
138        let mut counts = Vec::with_capacity(self.fields.len());
139
140        for (name, vtype) in &self.fields {
141            field_names.push(name.clone());
142            sizes.push(vtype.size());
143            types.push(value_type_to_char(*vtype));
144            counts.push(1);
145        }
146
147        let points = (width as usize) * (self.height as usize);
148
149        Ok(PcdHeader {
150            version: self.version,
151            fields: field_names,
152            sizes,
153            types,
154            counts,
155            width,
156            height: self.height,
157            viewpoint: self.viewpoint,
158            points,
159            data: self.data,
160        })
161    }
162}
163
164/// Convert ValueType to PCD type character.
165fn value_type_to_char(vtype: ValueType) -> char {
166    match vtype {
167        ValueType::I8 | ValueType::I16 | ValueType::I32 => 'I',
168        ValueType::U8 | ValueType::U16 | ValueType::U32 => 'U',
169        ValueType::F32 | ValueType::F64 => 'F',
170    }
171}