Skip to main content

lib3mf_core/model/
stats.rs

1use crate::model::Unit;
2use crate::utils::hardware::HardwareCapabilities;
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6/// Comprehensive statistics and metadata for a 3MF model.
7///
8/// Aggregates various statistics about the model's geometry, materials,
9/// production metadata, and vendor-specific information. Used by the CLI
10/// `stats` command and for model analysis.
11#[derive(Debug, Clone, Default, Serialize, Deserialize)]
12pub struct ModelStats {
13    /// Unit of measurement for the model
14    pub unit: Unit,
15    /// Software that generated the model (from metadata)
16    pub generator: Option<String>,
17    /// Custom metadata key-value pairs from the model
18    pub metadata: HashMap<String, String>,
19    /// Geometric statistics (vertices, triangles, volume, etc.)
20    pub geometry: GeometryStats,
21    /// Material and property statistics
22    pub materials: MaterialsStats,
23    /// Production extension metadata statistics
24    pub production: ProductionStats,
25    /// Displacement extension statistics
26    pub displacement: DisplacementStats,
27    /// Vendor-specific data (e.g., Bambu Studio project info)
28    pub vendor: VendorData,
29    /// System hardware capabilities info
30    pub system_info: HardwareCapabilities,
31    /// Thumbnail statistics
32    pub thumbnails: ThumbnailStats,
33}
34
35/// Statistics about thumbnails in a 3MF package.
36#[derive(Debug, Clone, Default, Serialize, Deserialize)]
37pub struct ThumbnailStats {
38    /// Whether a package-level thumbnail is present.
39    pub package_thumbnail_present: bool,
40    /// Number of object-level thumbnails in the package.
41    pub object_thumbnail_count: usize,
42}
43
44/// Statistics about material and property resources in a 3MF model.
45#[derive(Debug, Clone, Default, Serialize, Deserialize)]
46pub struct MaterialsStats {
47    /// Number of base material groups.
48    pub base_materials_count: usize,
49    /// Number of color groups.
50    pub color_groups_count: usize,
51    /// Number of 2D texture coordinate groups.
52    pub texture_2d_groups_count: usize,
53    /// Number of composite materials groups.
54    pub composite_materials_count: usize,
55    /// Number of multi-properties groups.
56    pub multi_properties_count: usize,
57}
58
59/// Geometric statistics for the model.
60///
61/// Aggregates counts and measurements of the model's geometry including
62/// vertices, triangles, bounding box, surface area, and volume.
63#[derive(Debug, Clone, Default, Serialize, Deserialize)]
64pub struct GeometryStats {
65    /// Total number of object resources
66    pub object_count: usize,
67    /// Number of build items (instances to print)
68    pub instance_count: usize,
69    /// Total number of triangles across all meshes
70    pub triangle_count: u64,
71    /// Total number of vertices across all meshes
72    pub vertex_count: u64,
73    /// Axis-aligned bounding box of the entire model
74    pub bounding_box: Option<BoundingBox>,
75    /// Total surface area in square model units
76    pub surface_area: f64,
77    /// Total volume in cubic model units
78    pub volume: f64,
79    /// Whether all meshes are manifold (watertight)
80    pub is_manifold: bool,
81    /// Count of objects by type (e.g., {"model": 5, "support": 2})
82    #[serde(default)]
83    pub type_counts: HashMap<String, usize>,
84}
85
86/// An axis-aligned bounding box in 3D space.
87///
88/// Represents the smallest box (aligned with coordinate axes) that
89/// contains all geometry. Useful for understanding model size and
90/// for spatial queries.
91#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
92pub struct BoundingBox {
93    /// Minimum corner coordinates [x, y, z]
94    pub min: [f32; 3],
95    /// Maximum corner coordinates [x, y, z]
96    pub max: [f32; 3],
97}
98
99impl BoundingBox {
100    /// Transforms the bounding box by the given 4x4 matrix, returning a new axis-aligned bounding box.
101    pub fn transform(&self, matrix: glam::Mat4) -> Self {
102        let corners = [
103            glam::Vec3::new(self.min[0], self.min[1], self.min[2]),
104            glam::Vec3::new(self.min[0], self.min[1], self.max[2]),
105            glam::Vec3::new(self.min[0], self.max[1], self.min[2]),
106            glam::Vec3::new(self.min[0], self.max[1], self.max[2]),
107            glam::Vec3::new(self.max[0], self.min[1], self.min[2]),
108            glam::Vec3::new(self.max[0], self.min[1], self.max[2]),
109            glam::Vec3::new(self.max[0], self.max[1], self.min[2]),
110            glam::Vec3::new(self.max[0], self.max[1], self.max[2]),
111        ];
112
113        let mut transformed_min = glam::Vec3::splat(f32::INFINITY);
114        let mut transformed_max = glam::Vec3::splat(f32::NEG_INFINITY);
115
116        for corner in corners {
117            let transformed = matrix.transform_point3(corner);
118            transformed_min = transformed_min.min(transformed);
119            transformed_max = transformed_max.max(transformed);
120        }
121
122        Self {
123            min: [transformed_min.x, transformed_min.y, transformed_min.z],
124            max: [transformed_max.x, transformed_max.y, transformed_max.z],
125        }
126    }
127}
128
129/// Statistics from the Production Extension (UUIDs).
130#[derive(Debug, Clone, Default, Serialize, Deserialize)]
131pub struct ProductionStats {
132    /// Number of objects and build items that have UUIDs assigned.
133    pub uuid_count: usize,
134}
135
136/// Statistics from the Displacement Extension.
137#[derive(Debug, Clone, Default, Serialize, Deserialize)]
138pub struct DisplacementStats {
139    /// Number of displacement meshes in the model.
140    pub mesh_count: usize,
141    /// Number of displacement texture resources.
142    pub texture_count: usize,
143    /// Total number of normal vectors across all displacement meshes.
144    pub normal_count: u64,
145    /// Total number of gradient vectors across all displacement meshes.
146    pub gradient_count: u64,
147    /// Total number of displaced triangles (triangles with displacement coordinate indices).
148    pub displaced_triangle_count: u64,
149    /// Total number of triangles in all displacement meshes.
150    pub total_triangle_count: u64,
151}
152
153/// Vendor-specific data extracted from Bambu Studio 3MF files.
154#[derive(Debug, Clone, Default, Serialize, Deserialize)]
155pub struct VendorData {
156    /// Printer model name from Bambu Studio metadata.
157    pub printer_model: Option<String>,
158    /// List of filament configurations used in the print.
159    pub filaments: Vec<FilamentInfo>,
160    /// List of plate/build plate configurations.
161    pub plates: Vec<PlateInfo>,
162    /// Estimated print time from the slicer.
163    pub print_time_estimate: Option<String>,
164    /// Version string of the slicer that generated the file.
165    pub slicer_version: Option<String>,
166    /// Nozzle diameter in millimeters.
167    pub nozzle_diameter: Option<f32>,
168    /// Warnings generated by the slicer during slicing.
169    pub slicer_warnings: Vec<SlicerWarning>,
170    /// Per-object metadata from Bambu Studio project files.
171    pub object_metadata: Vec<BambuObjectMetadata>,
172    /// Global project settings from Bambu Studio.
173    pub project_settings: Option<BambuProjectSettings>,
174    /// Slicer profile configurations embedded in the file.
175    pub profile_configs: Vec<BambuProfileConfig>,
176    /// Assembly information from Bambu Studio files.
177    pub assembly_info: Vec<AssemblyItem>,
178    /// Path to Bambu cover thumbnail (from OPC relationship), e.g., "Metadata/plate_1.png"
179    pub bambu_cover_thumbnail: Option<String>,
180    /// Path to Bambu embedded gcode (from OPC relationship), e.g., "Metadata/plate_1.gcode"
181    pub bambu_gcode: Option<String>,
182}
183
184/// Information about a single filament used in a Bambu Studio print.
185#[derive(Debug, Clone, Default, Serialize, Deserialize)]
186pub struct FilamentInfo {
187    /// Filament slot index.
188    pub id: u32,
189    /// Tray info index from the AMS system.
190    pub tray_info_idx: Option<String>,
191    /// Filament type string (e.g., `"PLA"`, `"PETG"`).
192    pub type_: String,
193    /// Display color in hex format.
194    pub color: Option<String>,
195    /// Estimated filament used in meters.
196    pub used_m: Option<f32>,
197    /// Estimated filament used in grams.
198    pub used_g: Option<f32>,
199}
200
201/// Information about a single build plate in a Bambu Studio project.
202#[derive(Debug, Clone, Default, Serialize, Deserialize)]
203pub struct PlateInfo {
204    /// Plate index (1-based).
205    pub id: u32,
206    /// Optional display name for this plate.
207    pub name: Option<String>,
208    /// Whether this plate is locked in the slicer.
209    pub locked: bool,
210    /// Path to the pre-sliced G-code file for this plate.
211    pub gcode_file: Option<String>,
212    /// Path to the thumbnail image for this plate.
213    pub thumbnail_file: Option<String>,
214    /// List of object instances on this plate.
215    pub items: Vec<PlateModelInstance>,
216}
217
218/// An instance of an object on a Bambu Studio build plate.
219#[derive(Debug, Clone, Default, Serialize, Deserialize)]
220pub struct PlateModelInstance {
221    /// Object resource ID.
222    pub object_id: u32,
223    /// Instance index for multi-instance objects.
224    pub instance_id: u32,
225    /// Optional identify ID from the Bambu project metadata.
226    pub identify_id: Option<u32>,
227}
228
229/// A warning message generated by the slicer during the slicing process.
230#[derive(Debug, Clone, Default, Serialize, Deserialize)]
231pub struct SlicerWarning {
232    /// Warning message text.
233    pub msg: String,
234    /// Warning severity level string.
235    pub level: Option<String>,
236    /// Machine-readable error code.
237    pub error_code: Option<String>,
238}
239
240/// Per-object metadata from a Bambu Studio project file.
241#[derive(Debug, Clone, Default, Serialize, Deserialize)]
242pub struct BambuObjectMetadata {
243    /// Object resource ID.
244    pub id: u32,
245    /// Object display name.
246    pub name: Option<String>,
247    /// Extruder index assigned to this object.
248    pub extruder: Option<u32>,
249    /// Number of triangular faces in this object.
250    pub face_count: Option<u64>,
251    /// Sub-part metadata for multi-part objects.
252    pub parts: Vec<BambuPartMetadata>,
253}
254
255/// Metadata for a single part within a Bambu Studio object.
256#[derive(Debug, Clone, Default, Serialize, Deserialize)]
257pub struct BambuPartMetadata {
258    /// Part index.
259    pub id: u32,
260    /// Part subtype (normal, modifier, support blocker/enforcer, etc.).
261    pub subtype: PartSubtype,
262    /// Part display name.
263    pub name: Option<String>,
264    /// 3x4 transform matrix string.
265    pub matrix: Option<String>,
266    /// Source volume information for the part.
267    pub source: Option<BambuPartSource>,
268    /// Mesh repair statistics.
269    pub mesh_stat: Option<BambuMeshStat>,
270    /// Per-part print setting overrides.
271    pub print_overrides: HashMap<String, String>,
272}
273
274/// Classification of a Bambu Studio object part.
275#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
276pub enum PartSubtype {
277    /// A normal printable part (default).
278    #[default]
279    NormalPart,
280    /// A modifier volume that changes settings in a region.
281    ModifierPart,
282    /// A support blocker volume.
283    SupportBlocker,
284    /// A support enforcer volume.
285    SupportEnforcer,
286    /// An unrecognized subtype string.
287    Other(String),
288}
289
290impl PartSubtype {
291    /// Parse a Bambu part subtype string into a `PartSubtype` variant.
292    pub fn parse(s: &str) -> Self {
293        match s {
294            "normal_part" => Self::NormalPart,
295            "modifier_part" => Self::ModifierPart,
296            "support_blocker" => Self::SupportBlocker,
297            "support_enforcer" => Self::SupportEnforcer,
298            other => Self::Other(other.to_string()),
299        }
300    }
301}
302
303/// Source volume information for a Bambu Studio part.
304#[derive(Debug, Clone, Default, Serialize, Deserialize)]
305pub struct BambuPartSource {
306    /// Source volume ID (used to identify the originating volume).
307    pub volume_id: Option<u32>,
308    /// X offset of the source volume.
309    pub offset_x: Option<f64>,
310    /// Y offset of the source volume.
311    pub offset_y: Option<f64>,
312    /// Z offset of the source volume.
313    pub offset_z: Option<f64>,
314}
315
316/// Mesh repair statistics from Bambu Studio's automatic repair.
317#[derive(Debug, Clone, Default, Serialize, Deserialize)]
318pub struct BambuMeshStat {
319    /// Number of edges fixed during repair.
320    pub edges_fixed: Option<u32>,
321    /// Number of degenerate faces removed.
322    pub degenerate_facets: Option<u32>,
323    /// Number of faces removed during repair.
324    pub facets_removed: Option<u32>,
325    /// Number of faces whose winding was reversed.
326    pub facets_reversed: Option<u32>,
327    /// Number of backwards edges corrected.
328    pub backwards_edges: Option<u32>,
329}
330
331/// Global project settings from a Bambu Studio project file.
332#[derive(Debug, Clone, Default, Serialize, Deserialize)]
333pub struct BambuProjectSettings {
334    /// Printer model name.
335    pub printer_model: Option<String>,
336    /// Name of the printer profile this inherits from.
337    pub printer_inherits: Option<String>,
338    /// Bed/plate type (e.g., `"Engineering Plate"`).
339    pub bed_type: Option<String>,
340    /// Layer height in millimeters.
341    pub layer_height: Option<f32>,
342    /// First layer height in millimeters.
343    pub first_layer_height: Option<f32>,
344    /// Filament type strings per extruder.
345    pub filament_type: Vec<String>,
346    /// Filament display colors per extruder.
347    pub filament_colour: Vec<String>,
348    /// Nozzle diameters per extruder.
349    pub nozzle_diameter: Vec<f32>,
350    /// Print sequence (e.g., `"by_layer"` or `"by_object"`).
351    pub print_sequence: Option<String>,
352    /// Number of perimeter walls.
353    pub wall_loops: Option<u32>,
354    /// Infill density percentage string.
355    pub infill_density: Option<String>,
356    /// Support generation type.
357    pub support_type: Option<String>,
358    /// Additional key-value settings not covered by named fields.
359    pub extras: HashMap<String, serde_json::Value>,
360}
361
362/// A slicer profile configuration embedded in a Bambu Studio project file.
363#[derive(Debug, Clone, Default, Serialize, Deserialize)]
364pub struct BambuProfileConfig {
365    /// Profile category: `"filament"`, `"machine"`, or `"process"`.
366    pub config_type: String, // "filament", "machine", "process"
367    /// Index suffix (the N in `filament_settings_N.config`).
368    pub index: u32, // the N in filament_settings_N.config
369    /// Name of the profile this inherits from.
370    pub inherits: Option<String>,
371    /// Display name of this profile.
372    pub name: Option<String>,
373    /// Additional settings not covered by named fields.
374    pub extras: HashMap<String, serde_json::Value>,
375}
376
377/// An assembly item from a Bambu Studio project file.
378#[derive(Debug, Clone, Default, Serialize, Deserialize)]
379pub struct AssemblyItem {
380    /// Object resource ID.
381    pub object_id: u32,
382    /// Number of instances of this object in the assembly.
383    pub instance_count: u32,
384    /// Transform matrix string.
385    pub transform: Option<String>,
386    /// Offset string.
387    pub offset: Option<String>,
388}