lib3mf_core/utils/
diff.rs1use crate::model::Model;
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Default, Serialize, Deserialize)]
5pub struct ModelDiff {
6 pub metadata_diffs: Vec<MetadataDiff>,
7 pub resource_diffs: Vec<ResourceDiff>,
8 pub build_diffs: Vec<BuildDiff>,
9}
10
11#[derive(Debug, Serialize, Deserialize)]
12pub struct MetadataDiff {
13 pub key: String,
14 pub old_value: Option<String>,
15 pub new_value: Option<String>,
16}
17
18#[derive(Debug, Serialize, Deserialize)]
19pub enum ResourceDiff {
20 Added { id: u32, type_name: String },
21 Removed { id: u32, type_name: String },
22 Changed { id: u32, details: Vec<String> },
23}
24
25#[derive(Debug, Serialize, Deserialize)]
26pub enum BuildDiff {
27 Added {
28 object_id: u32,
29 },
30 Removed {
31 object_id: u32,
32 },
33 Changed {
34 object_id: u32,
35 details: Vec<String>,
36 },
37}
38
39impl ModelDiff {
40 pub fn is_empty(&self) -> bool {
41 self.metadata_diffs.is_empty()
42 && self.resource_diffs.is_empty()
43 && self.build_diffs.is_empty()
44 }
45}
46
47pub fn compare_models(model_a: &Model, model_b: &Model) -> ModelDiff {
48 let mut diff = ModelDiff::default();
49
50 let mut all_keys: Vec<_> = model_a.metadata.keys().collect();
53 for k in model_b.metadata.keys() {
54 if !all_keys.contains(&k) {
55 all_keys.push(k);
56 }
57 }
58 all_keys.sort();
59 all_keys.dedup();
60
61 for key in all_keys {
62 let val_a = model_a.metadata.get(key);
63 let val_b = model_b.metadata.get(key);
64
65 if val_a != val_b {
66 diff.metadata_diffs.push(MetadataDiff {
67 key: key.clone(),
68 old_value: val_a.cloned(),
69 new_value: val_b.cloned(),
70 });
71 }
72 }
73
74 let resources_a = &model_a.resources;
84 let resources_b = &model_b.resources;
85
86 for res_a in resources_a.iter_objects() {
88 match resources_b.get_object(res_a.id) {
89 Some(res_b) => {
90 let type_a = get_geometry_type_name(&res_a.geometry);
91 let type_b = get_geometry_type_name(&res_b.geometry);
92
93 if type_a != type_b {
94 diff.resource_diffs.push(ResourceDiff::Changed {
95 id: res_a.id.0,
96 details: vec![format!("Type changed: {} -> {}", type_a, type_b)],
97 });
98 } else {
99 if let (
101 crate::model::Geometry::Mesh(mesh_a),
102 crate::model::Geometry::Mesh(mesh_b),
103 ) = (&res_a.geometry, &res_b.geometry)
104 {
105 let mut details = Vec::new();
106 if mesh_a.vertices.len() != mesh_b.vertices.len() {
107 details.push(format!(
108 "Vertex count changed: {} -> {}",
109 mesh_a.vertices.len(),
110 mesh_b.vertices.len()
111 ));
112 }
113 if mesh_a.triangles.len() != mesh_b.triangles.len() {
114 details.push(format!(
115 "Triangle count changed: {} -> {}",
116 mesh_a.triangles.len(),
117 mesh_b.triangles.len()
118 ));
119 }
120 if !details.is_empty() {
123 diff.resource_diffs.push(ResourceDiff::Changed {
124 id: res_a.id.0,
125 details,
126 });
127 }
128 }
129 }
130 }
131 None => {
132 diff.resource_diffs.push(ResourceDiff::Removed {
133 id: res_a.id.0,
134 type_name: get_geometry_type_name(&res_a.geometry).to_string(),
135 });
136 }
137 }
138 }
139
140 for res_b in resources_b.iter_objects() {
142 if !resources_a.exists(res_b.id) {
143 diff.resource_diffs.push(ResourceDiff::Added {
144 id: res_b.id.0,
145 type_name: get_geometry_type_name(&res_b.geometry).to_string(),
146 });
147 }
148 }
149
150 if model_a.build.items.len() != model_b.build.items.len() {
159 diff.build_diffs.push(BuildDiff::Changed {
160 object_id: 0, details: vec![format!(
162 "Item count changed: {} -> {}",
163 model_a.build.items.len(),
164 model_b.build.items.len()
165 )],
166 });
167 }
168
169 diff
170}
171
172fn get_geometry_type_name(g: &crate::model::Geometry) -> &'static str {
173 match g {
174 crate::model::Geometry::Mesh(_) => "Mesh",
175 crate::model::Geometry::Components(_) => "Components",
176 crate::model::Geometry::SliceStack(_) => "SliceStack",
177 crate::model::Geometry::VolumetricStack(_) => "VolumetricStack",
178 crate::model::Geometry::BooleanShape(_) => "BooleanShape",
179 crate::model::Geometry::DisplacementMesh(_) => "DisplacementMesh",
180 }
181}