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}