Skip to main content

altium_format/records/pcb/
component_body.rs

1//! PCB ComponentBody record.
2
3use std::io::Read;
4
5use altium_format_derive::AltiumRecord;
6
7use super::{PcbOutline, primitive::PcbPrimitiveCommon};
8use crate::error::Result;
9use crate::traits::FromBinary;
10use crate::types::{Coord, CoordPoint, CoordRect, ParameterCollection};
11
12/// PCB Component Body primitive (3D model reference).
13#[derive(Debug, Clone, Default)]
14pub struct PcbComponentBody {
15    /// Common primitive fields.
16    pub common: PcbPrimitiveCommon,
17    /// Outline vertices.
18    pub outline: Vec<CoordPoint>,
19    /// V7 layer name.
20    pub v7_layer: String,
21    /// Body name.
22    pub name: String,
23    /// Kind.
24    pub kind: i32,
25    /// Sub-polygon index.
26    pub sub_poly_index: i32,
27    /// Union index.
28    pub union_index: i32,
29    /// Arc resolution.
30    pub arc_resolution: Coord,
31    /// Whether shape-based.
32    pub is_shape_based: bool,
33    /// Stand-off height.
34    pub standoff_height: Coord,
35    /// Overall height.
36    pub overall_height: Coord,
37    /// Body projection type.
38    pub body_projection: i32,
39    /// 3D body color (COLORREF).
40    pub body_color_3d: i32,
41    /// 3D body opacity (0.0 - 1.0).
42    pub body_opacity_3d: f64,
43    /// Unique identifier for the component body.
44    pub unique_id: String,
45    /// Texture name.
46    pub texture: String,
47    /// Texture center.
48    pub texture_center: CoordPoint,
49    /// Texture size.
50    pub texture_size: CoordPoint,
51    /// Texture rotation.
52    pub texture_rotation: f64,
53    /// Model ID (links to STEP model).
54    pub model_id: String,
55    /// Model checksum.
56    pub model_checksum: i32,
57    /// Whether model is embedded.
58    pub model_embed: bool,
59    /// 2D model location.
60    pub model_2d_location: CoordPoint,
61    /// 2D model rotation.
62    pub model_2d_rotation: f64,
63    /// 3D model rotation X.
64    pub model_3d_rot_x: f64,
65    /// 3D model rotation Y.
66    pub model_3d_rot_y: f64,
67    /// 3D model rotation Z.
68    pub model_3d_rot_z: f64,
69    /// 3D model Z offset.
70    pub model_3d_dz: Coord,
71    /// Model snap count.
72    pub model_snap_count: i32,
73    /// Model type.
74    pub model_type: i32,
75    /// STEP model data (if loaded).
76    pub step_model: Option<String>,
77}
78
79#[derive(Debug, Clone, Default, AltiumRecord)]
80#[altium(format = "binary")]
81struct PcbComponentBodyBinary {
82    #[altium(flatten)]
83    common: PcbPrimitiveCommon,
84    _unknown1: u32,
85    _unknown2: u8,
86    parameters: ParameterCollection,
87    outline: PcbOutline,
88}
89
90impl FromBinary for PcbComponentBody {
91    fn read_from<R: Read>(reader: &mut R) -> Result<Self> {
92        let raw = <PcbComponentBodyBinary as FromBinary>::read_from(reader)?;
93        let mut body = PcbComponentBody {
94            common: raw.common,
95            outline: raw.outline.into(),
96            ..Default::default()
97        };
98        body.import_from_parameters(&raw.parameters);
99        Ok(body)
100    }
101}
102
103impl PcbComponentBody {
104    /// Import fields from parameters.
105    fn import_from_parameters(&mut self, p: &ParameterCollection) {
106        self.v7_layer = p
107            .get("V7_LAYER")
108            .map(|v| v.as_str().to_string())
109            .unwrap_or_default();
110        self.name = p
111            .get("NAME")
112            .map(|v| v.as_str().to_string())
113            .unwrap_or_default();
114        self.kind = p.get("KIND").map(|v| v.as_int_or(0)).unwrap_or(0);
115        self.sub_poly_index = p.get("SUBPOLYINDEX").map(|v| v.as_int_or(-1)).unwrap_or(-1);
116        self.union_index = p.get("UNIONINDEX").map(|v| v.as_int_or(0)).unwrap_or(0);
117        self.arc_resolution =
118            Coord::from_raw(p.get("ARCRESOLUTION").map(|v| v.as_int_or(0)).unwrap_or(0));
119        self.is_shape_based = p
120            .get("ISSHAPEBASED")
121            .map(|v| v.as_bool_or(false))
122            .unwrap_or(false);
123        self.standoff_height =
124            Coord::from_raw(p.get("STANDOFFHEIGHT").map(|v| v.as_int_or(0)).unwrap_or(0));
125        self.overall_height =
126            Coord::from_raw(p.get("OVERALLHEIGHT").map(|v| v.as_int_or(0)).unwrap_or(0));
127        self.body_projection = p.get("BODYPROJECTION").map(|v| v.as_int_or(0)).unwrap_or(0);
128        self.body_color_3d = p
129            .get("BODYCOLOR3D")
130            .map(|v| v.as_int_or(14737632))
131            .unwrap_or(14737632);
132        self.body_opacity_3d = p
133            .get("BODYOPACITY3D")
134            .map(|v| v.as_double_or(1.0))
135            .unwrap_or(1.0);
136
137        if let Some(id_str) = p.get("IDENTIFIER") {
138            let chars: Vec<char> = id_str
139                .as_str()
140                .split(',')
141                .filter_map(|s| s.parse::<u32>().ok())
142                .filter_map(char::from_u32)
143                .collect();
144            self.unique_id = chars.into_iter().collect();
145        }
146
147        self.texture = p
148            .get("TEXTURE")
149            .map(|v| v.as_str().to_string())
150            .unwrap_or_default();
151        self.texture_center = CoordPoint::from_raw(
152            p.get("TEXTURECENTERX").map(|v| v.as_int_or(0)).unwrap_or(0),
153            p.get("TEXTURECENTERY").map(|v| v.as_int_or(0)).unwrap_or(0),
154        );
155        self.texture_size = CoordPoint::from_raw(
156            p.get("TEXTURESIZEX").map(|v| v.as_int_or(0)).unwrap_or(0),
157            p.get("TEXTURESIZEY").map(|v| v.as_int_or(0)).unwrap_or(0),
158        );
159        self.texture_rotation = p
160            .get("TEXTUREROTATION")
161            .map(|v| v.as_double_or(0.0))
162            .unwrap_or(0.0);
163        self.model_id = p
164            .get("MODELID")
165            .map(|v| v.as_str().to_string())
166            .unwrap_or_default();
167        self.model_checksum = p.get("MODEL.CHECKSUM").map(|v| v.as_int_or(0)).unwrap_or(0);
168        self.model_embed = p
169            .get("MODEL.EMBED")
170            .map(|v| v.as_bool_or(true))
171            .unwrap_or(true);
172        self.model_2d_location = CoordPoint::from_raw(
173            p.get("MODEL.2D.X").map(|v| v.as_int_or(0)).unwrap_or(0),
174            p.get("MODEL.2D.Y").map(|v| v.as_int_or(0)).unwrap_or(0),
175        );
176        self.model_2d_rotation = p
177            .get("MODEL.2D.ROTATION")
178            .map(|v| v.as_double_or(0.0))
179            .unwrap_or(0.0);
180        self.model_3d_rot_x = p
181            .get("MODEL.3D.ROTX")
182            .map(|v| v.as_double_or(0.0))
183            .unwrap_or(0.0);
184        self.model_3d_rot_y = p
185            .get("MODEL.3D.ROTY")
186            .map(|v| v.as_double_or(0.0))
187            .unwrap_or(0.0);
188        self.model_3d_rot_z = p
189            .get("MODEL.3D.ROTZ")
190            .map(|v| v.as_double_or(0.0))
191            .unwrap_or(0.0);
192        self.model_3d_dz =
193            Coord::from_raw(p.get("MODEL.3D.DZ").map(|v| v.as_int_or(0)).unwrap_or(0));
194        self.model_snap_count = p
195            .get("MODEL.SNAPCOUNT")
196            .map(|v| v.as_int_or(0))
197            .unwrap_or(0);
198        self.model_type = p
199            .get("MODEL.MODELTYPE")
200            .map(|v| v.as_int_or(1))
201            .unwrap_or(1);
202    }
203
204    /// Calculate the bounding rectangle.
205    pub fn calculate_bounds(&self) -> CoordRect {
206        if self.outline.is_empty() {
207            return CoordRect::default();
208        }
209
210        let min_x = self.outline.iter().map(|p| p.x.to_raw()).min().unwrap_or(0);
211        let max_x = self.outline.iter().map(|p| p.x.to_raw()).max().unwrap_or(0);
212        let min_y = self.outline.iter().map(|p| p.y.to_raw()).min().unwrap_or(0);
213        let max_y = self.outline.iter().map(|p| p.y.to_raw()).max().unwrap_or(0);
214
215        CoordRect::from_raw(min_x, min_y, max_x - min_x, max_y - min_y)
216    }
217
218    /// Export fields to parameters.
219    fn export_to_parameters(&self) -> ParameterCollection {
220        let mut p = ParameterCollection::new();
221
222        if !self.v7_layer.is_empty() {
223            p.add("V7_LAYER", &self.v7_layer);
224        }
225        if !self.name.is_empty() {
226            p.add("NAME", &self.name);
227        }
228        p.add_int("KIND", self.kind);
229        p.add_int("SUBPOLYINDEX", self.sub_poly_index);
230        p.add_int("UNIONINDEX", self.union_index);
231        p.add_int("ARCRESOLUTION", self.arc_resolution.to_raw());
232        p.add("ISSHAPEBASED", if self.is_shape_based { "T" } else { "F" });
233        p.add_int("STANDOFFHEIGHT", self.standoff_height.to_raw());
234        p.add_int("OVERALLHEIGHT", self.overall_height.to_raw());
235        p.add_int("BODYPROJECTION", self.body_projection);
236        p.add_int("BODYCOLOR3D", self.body_color_3d);
237        p.add("BODYOPACITY3D", &format!("{}", self.body_opacity_3d));
238
239        if !self.unique_id.is_empty() {
240            let codes: Vec<String> = self
241                .unique_id
242                .chars()
243                .map(|c| (c as u32).to_string())
244                .collect();
245            p.add("IDENTIFIER", &codes.join(","));
246        }
247
248        if !self.texture.is_empty() {
249            p.add("TEXTURE", &self.texture);
250        }
251        p.add_int("TEXTURECENTERX", self.texture_center.x.to_raw());
252        p.add_int("TEXTURECENTERY", self.texture_center.y.to_raw());
253        p.add_int("TEXTURESIZEX", self.texture_size.x.to_raw());
254        p.add_int("TEXTURESIZEY", self.texture_size.y.to_raw());
255        p.add("TEXTUREROTATION", &format!("{}", self.texture_rotation));
256
257        if !self.model_id.is_empty() {
258            p.add("MODELID", &self.model_id);
259        }
260        p.add_int("MODEL.CHECKSUM", self.model_checksum);
261        p.add("MODEL.EMBED", if self.model_embed { "T" } else { "F" });
262        p.add_int("MODEL.2D.X", self.model_2d_location.x.to_raw());
263        p.add_int("MODEL.2D.Y", self.model_2d_location.y.to_raw());
264        p.add("MODEL.2D.ROTATION", &format!("{}", self.model_2d_rotation));
265        p.add("MODEL.3D.ROTX", &format!("{}", self.model_3d_rot_x));
266        p.add("MODEL.3D.ROTY", &format!("{}", self.model_3d_rot_y));
267        p.add("MODEL.3D.ROTZ", &format!("{}", self.model_3d_rot_z));
268        p.add_int("MODEL.3D.DZ", self.model_3d_dz.to_raw());
269        p.add_int("MODEL.SNAPCOUNT", self.model_snap_count);
270        p.add_int("MODEL.MODELTYPE", self.model_type);
271
272        p
273    }
274}
275
276use crate::traits::ToBinary;
277use std::io::Write;
278
279impl ToBinary for PcbComponentBody {
280    fn write_to<W: Write>(&self, writer: &mut W) -> Result<()> {
281        use byteorder::{LittleEndian, WriteBytesExt};
282
283        // Common primitive fields
284        self.common.write_to(writer)?;
285
286        // Unknown fields
287        writer.write_u32::<LittleEndian>(0)?; // _unknown1
288        writer.write_u8(0)?; // _unknown2
289
290        // Parameters
291        let params = self.export_to_parameters();
292        params.write_to(writer)?;
293
294        // Outline
295        let outline: PcbOutline = self.outline.clone().into();
296        outline.write_to(writer)?;
297
298        Ok(())
299    }
300
301    fn binary_size(&self) -> usize {
302        let outline: PcbOutline = self.outline.clone().into();
303        let params = self.export_to_parameters();
304        13 + 5 + params.binary_size() + outline.binary_size()
305    }
306}