Another project
0

Configure Feed

Select the types of activity you want to include in your feed.

feat(kernel): persist & reattach brep labels through solids

Lewis: May this revision serve well! <lu5a@proton.me>

author
Lewis
date (May 31, 2026, 10:56 AM +0300) commit d2485139 parent f3567e03 change-id kwwortly
+422 -27
+4 -1
crates/bone-kernel/Cargo.toml
··· 6 6 rust-version.workspace = true 7 7 8 8 [dependencies] 9 + blake3 = { workspace = true } 9 10 bone-types = { workspace = true } 11 + ron = { workspace = true } 10 12 serde = { workspace = true } 11 13 slotmap = { workspace = true } 12 14 thiserror = { workspace = true } 13 15 truck-meshalgo = { version = "=0.4.0", default-features = false, features = ["tessellation"] } 14 16 truck-modeling = "=0.6.0" 17 + truck-stepio = "=0.3.0" 18 + truck-topology = "=0.6.0" 15 19 uom = { workspace = true } 16 20 17 21 [dev-dependencies] 18 22 insta = { workspace = true } 19 23 proptest = { workspace = true } 20 - ron = { workspace = true } 21 24 22 25 [lints] 23 26 workspace = true
+7 -1
crates/bone-kernel/src/arc3.rs
··· 1 + use bone_types::dimensioned_serde; 1 2 use bone_types::{ 2 3 Aabb3, Angle, AngleTolerance, ChordHeightTolerance, Length, Parameter, Plane3, Point3, 3 4 Tolerance, UnitVec3, Vec3, 4 5 }; 5 6 use core::f64::consts::TAU; 7 + use serde::{Deserialize, Serialize}; 6 8 use uom::si::angle::radian; 7 9 use uom::si::length::millimeter; 8 10 ··· 13 15 use crate::closest::ClosestPoint3; 14 16 use crate::curve3::{Curve3, Curve3Kind}; 15 17 16 - #[derive(Copy, Clone, Debug, PartialEq)] 18 + #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] 19 + #[serde(deny_unknown_fields)] 17 20 pub struct Arc3 { 18 21 plane: Plane3, 22 + #[serde(with = "dimensioned_serde::length_si")] 19 23 radius: Length, 24 + #[serde(with = "dimensioned_serde::angle_si")] 20 25 start_angle: Angle, 26 + #[serde(with = "dimensioned_serde::angle_si")] 21 27 sweep_angle: Angle, 22 28 } 23 29
+19 -16
crates/bone-kernel/src/brep/build.rs
··· 2 2 use std::collections::HashSet; 3 3 4 4 use bone_types::{ 5 - BrepEdgeId, BrepFaceId, BrepLoopId, BrepShellId, BrepVertexId, CreaseAngle, EdgeLabel, EdgeRole, 6 - FaceLabel, Parameter, Point3, SideKind, SketchEntityId, Tolerance, UnitVec3, VertexLabel, 7 - VertexRole, 5 + BrepEdgeId, BrepFaceId, BrepLoopId, BrepShellId, BrepVertexId, CreaseAngle, EdgeLabel, 6 + EdgeRole, FaceLabel, Parameter, Point3, SideKind, SketchEntityId, Tolerance, UnitVec3, 7 + VertexLabel, VertexRole, 8 8 }; 9 9 use slotmap::{Key, SlotMap}; 10 10 use truck_modeling::{ ··· 13 13 }; 14 14 15 15 use super::convert::{point_from_truck, try_unit_from_truck}; 16 - use super::edges::{crease_from_normals, line_between, EdgeCurve3}; 16 + use super::edges::{EdgeCurve3, crease_from_normals, line_between}; 17 17 use super::{BrepEdge, BrepError, BrepFace, BrepLoop, BrepShell, BrepSolid, BrepVertex, LabelKind}; 18 18 use crate::curve3::Curve3; 19 19 ··· 116 116 .flat_map(|face_id| faces.faces[*face_id].loops().iter().copied()) 117 117 .collect(); 118 118 119 + let reattach = super::persist::capture(&solid, labeling)?; 120 + 119 121 Ok(BrepSolid { 120 122 arena: Arena { 121 123 solid, ··· 132 134 loop_order, 133 135 edge_order, 134 136 vertex_order, 137 + reattach, 135 138 }) 136 139 } 137 140 138 141 fn gather_truck_face_adjacency(solid: &Solid) -> HashMap<EdgeID, Vec<FaceID>> { 139 - solid 140 - .boundaries() 141 - .iter() 142 - .flat_map(Shell::face_iter) 143 - .fold(HashMap::<EdgeID, Vec<FaceID>>::new(), |mut adj, face| { 142 + solid.boundaries().iter().flat_map(Shell::face_iter).fold( 143 + HashMap::<EdgeID, Vec<FaceID>>::new(), 144 + |mut adj, face| { 144 145 face.boundaries().iter().for_each(|wire| { 145 146 wire.edge_iter().for_each(|edge| { 146 147 adj.entry(edge.id()).or_default().push(face.id()); 147 148 }); 148 149 }); 149 150 adj 150 - }) 151 + }, 152 + ) 151 153 } 152 154 153 155 fn ordered_by<K: Key, V, T: Ord>(map: &SlotMap<K, V>, key: impl Fn(&V) -> T) -> Vec<K> { ··· 367 369 let b_dist = vertex_positions 368 370 .get(b) 369 371 .map_or(f64::INFINITY, |pos| squared_distance(*pos, start)); 370 - if a_dist <= b_dist { 371 - [*a, *b] 372 - } else { 373 - [*b, *a] 374 - } 372 + if a_dist <= b_dist { [*a, *b] } else { [*b, *a] } 375 373 } else { 376 - let representative = counts.keys().copied().min().unwrap_or_default(); 374 + let representative = counts 375 + .keys() 376 + .copied() 377 + .min() 378 + .or_else(|| vertex_positions.keys().copied().min()) 379 + .unwrap_or_default(); 377 380 [representative, representative] 378 381 } 379 382 }
+3 -1
crates/bone-kernel/src/brep/edges.rs
··· 2 2 Aabb3, BrepEdgeId, ChordHeightTolerance, CreaseAngle, EdgeLabel, EdgeRole, Parameter, Plane3, 3 3 Point2, Point3, SideKind, Tolerance, UnitVec3, Vec3, 4 4 }; 5 + use serde::{Deserialize, Serialize}; 5 6 use std::ops::Deref; 6 7 7 8 use crate::arc3::Arc3; ··· 11 12 use crate::curve3::Curve3; 12 13 use crate::line3::Line3; 13 14 14 - #[derive(Clone, Debug, PartialEq)] 15 + #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] 16 + #[serde(deny_unknown_fields)] 15 17 pub enum EdgeCurve3 { 16 18 Line(Line3), 17 19 Arc(Arc3),
+1 -1
crates/bone-kernel/src/brep/eval.rs
··· 13 13 use crate::intersect::{IntersectionSet, intersect_curves}; 14 14 15 15 use super::build::{SolidLabeling, assemble}; 16 - use super::edges::{lift_curve2, EdgeCurve3}; 16 + use super::edges::{EdgeCurve3, lift_curve2}; 17 17 use super::profile::{ExtrudeProfile, ProfileLoop}; 18 18 use super::{BrepError, BrepSolid, ProfileDefect, TruckGap}; 19 19
+79 -2
crates/bone-kernel/src/brep/mod.rs
··· 2 2 3 3 use bone_types::{ 4 4 Aabb3, AngleTolerance, BrepEdgeId, BrepFaceId, BrepLoopId, BrepShellId, BrepVertexId, 5 - ChordHeightTolerance, CreaseAngle, EdgeLabel, EdgeRole, FaceLabel, Point3, SideKind, Tolerance, 6 - VertexLabel, 5 + ChordHeightTolerance, CreaseAngle, EdgeLabel, EdgeRole, FaceLabel, Point3, SideKind, 6 + StepEntityKind, Tolerance, VertexLabel, 7 7 }; 8 8 use slotmap::SlotMap; 9 9 use truck_modeling::ShellCondition; ··· 12 12 pub(crate) mod convert; 13 13 mod edges; 14 14 pub mod eval; 15 + pub mod persist; 15 16 pub mod profile; 17 + pub mod step; 16 18 pub mod tessellate; 17 19 use crate::curve3::Curve3; 18 20 use build::{Arena, BoundaryIndex, EdgeArenaHandle, edge_length, edge_points}; 19 21 use convert::point_from_truck; 20 22 pub use edges::{EdgeCurve3, EdgePolyline, EdgePolylines}; 23 + pub use persist::{BrepReattach, EdgeReattach}; 21 24 pub use tessellate::{FaceMesh, MeshError, SolidMesh}; 22 25 23 26 #[derive(Debug, Clone, Copy, PartialEq, Eq)] ··· 109 112 TruckUnsupported { detail: TruckGap }, 110 113 #[error("{kind} carries no extrude label")] 111 114 MissingLabel { kind: LabelKind }, 115 + #[error("solid has {found} {kind} entries, reattach payload carries {expected}")] 116 + ReattachMismatch { 117 + kind: LabelKind, 118 + found: usize, 119 + expected: usize, 120 + }, 121 + #[error("solid geometry blob could not be serialized")] 122 + BlobSerialize, 123 + #[error("solid geometry blob could not be parsed")] 124 + BlobParse, 125 + #[error("STEP text is not valid ISO-10303-21")] 126 + StepSyntax, 127 + #[error("STEP file has no data section")] 128 + StepNoData, 129 + #[error("STEP shell could not be reconstructed")] 130 + StepShellMalformed, 131 + #[error("STEP file carries no solid shell")] 132 + StepEmpty, 133 + #[error("STEP file carries {count} shells; only a single closed shell is supported")] 134 + StepMultipleShells { count: usize }, 135 + #[error("STEP geometry uses {kind}, which the facade does not yet bridge")] 136 + StepUnsupported { kind: StepEntityKind }, 112 137 } 113 138 114 139 #[derive(Clone, Debug)] ··· 246 271 loop_order: Vec<BrepLoopId>, 247 272 edge_order: Vec<BrepEdgeId>, 248 273 vertex_order: Vec<BrepVertexId>, 274 + reattach: persist::BrepReattach, 249 275 } 250 276 251 277 impl BrepSolid { ··· 406 432 let edge = builder::tsweep(&corner, Vector3::new(1.0, 0.0, 0.0)); 407 433 let face = builder::tsweep(&edge, Vector3::new(0.0, 1.0, 0.0)); 408 434 builder::tsweep(&face, Vector3::new(0.0, 0.0, 1.0)) 435 + } 436 + 437 + fn iteration_fingerprint(solid: &Solid) -> String { 438 + let faces = solid 439 + .boundaries() 440 + .iter() 441 + .flat_map(Shell::face_iter) 442 + .map(|face| { 443 + let centroid = face 444 + .boundaries() 445 + .iter() 446 + .flat_map(|wire| wire.vertex_iter().map(|v| v.point())) 447 + .fold((0.0, 0.0, 0.0, 0.0), |(x, y, z, n), p| { 448 + (x + p.x, y + p.y, z + p.z, n + 1.0) 449 + }); 450 + format!("f({:.3},{:.3},{:.3})", centroid.0, centroid.1, centroid.2) 451 + }) 452 + .collect::<Vec<_>>() 453 + .join(" "); 454 + let vertices = solid 455 + .vertex_iter() 456 + .map(|v| { 457 + let p = v.point(); 458 + format!("v({:.3},{:.3},{:.3})", p.x, p.y, p.z) 459 + }) 460 + .collect::<Vec<_>>() 461 + .join(" "); 462 + let edges = solid 463 + .edge_iter() 464 + .map(|e| { 465 + let a = e.front().point(); 466 + let b = e.back().point(); 467 + format!("e({:.3},{:.3}->{:.3},{:.3})", a.x, a.y, b.x, b.y) 468 + }) 469 + .collect::<Vec<_>>() 470 + .join(" "); 471 + format!("faces:{faces}\nverts:{vertices}\nedges:{edges}") 472 + } 473 + 474 + #[test] 475 + fn truck_solid_iteration_order_survives_serde() { 476 + let solid = unit_cube(); 477 + let before = iteration_fingerprint(&solid); 478 + let Ok(text) = ron::to_string(&solid) else { 479 + panic!("serialize truck solid"); 480 + }; 481 + let Ok(restored) = ron::from_str::<Solid>(&text) else { 482 + panic!("deserialize truck solid"); 483 + }; 484 + let after = iteration_fingerprint(&restored); 485 + assert_eq!(before, after); 409 486 } 410 487 411 488 fn corner_index(x: f64, y: f64) -> usize {
+298
crates/bone-kernel/src/brep/persist.rs
··· 1 + use std::collections::HashSet; 2 + 3 + use bone_types::{EdgeLabel, FaceLabel, SketchEntityId, SolidKey, VertexLabel}; 4 + use ron::extensions::Extensions; 5 + use serde::{Deserialize, Serialize}; 6 + use truck_modeling::{EdgeID, Face, FaceID, Shell, Solid, VertexID}; 7 + 8 + use super::build::{SolidLabeling, assemble}; 9 + use super::edges::EdgeCurve3; 10 + use super::{BrepError, BrepSolid, LabelKind}; 11 + 12 + const GRID_DECIMALS: usize = 6; 13 + 14 + fn blob_options() -> ron::Options { 15 + ron::Options::default().with_default_extension(Extensions::UNWRAP_NEWTYPES) 16 + } 17 + 18 + #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] 19 + #[serde(deny_unknown_fields)] 20 + pub struct EdgeReattach { 21 + pub label: EdgeLabel, 22 + pub curve: Option<EdgeCurve3>, 23 + } 24 + 25 + #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] 26 + #[serde(deny_unknown_fields)] 27 + pub struct BrepReattach { 28 + faces: Vec<FaceLabel>, 29 + edges: Vec<EdgeReattach>, 30 + vertices: Vec<VertexLabel>, 31 + closed_curves: Vec<SketchEntityId>, 32 + } 33 + 34 + impl BrepReattach { 35 + #[must_use] 36 + pub fn faces(&self) -> &[FaceLabel] { 37 + &self.faces 38 + } 39 + 40 + #[must_use] 41 + pub fn edges(&self) -> &[EdgeReattach] { 42 + &self.edges 43 + } 44 + 45 + #[must_use] 46 + pub fn vertices(&self) -> &[VertexLabel] { 47 + &self.vertices 48 + } 49 + } 50 + 51 + pub(super) fn ordered_faces(solid: &Solid) -> Vec<FaceID> { 52 + solid 53 + .boundaries() 54 + .iter() 55 + .flat_map(Shell::face_iter) 56 + .map(Face::id) 57 + .collect() 58 + } 59 + 60 + pub(super) fn ordered_edges(solid: &Solid) -> Vec<EdgeID> { 61 + let mut seen = HashSet::new(); 62 + solid 63 + .edge_iter() 64 + .map(|edge| edge.id()) 65 + .filter(|id| seen.insert(*id)) 66 + .collect() 67 + } 68 + 69 + pub(super) fn ordered_vertices(solid: &Solid) -> Vec<VertexID> { 70 + let mut seen = HashSet::new(); 71 + solid 72 + .vertex_iter() 73 + .map(|vertex| vertex.id()) 74 + .filter(|id| seen.insert(*id)) 75 + .collect() 76 + } 77 + 78 + pub(crate) fn capture(solid: &Solid, labeling: &SolidLabeling) -> Result<BrepReattach, BrepError> { 79 + let faces = ordered_faces(solid) 80 + .into_iter() 81 + .map(|id| { 82 + labeling 83 + .faces 84 + .get(&id) 85 + .copied() 86 + .ok_or(BrepError::MissingLabel { 87 + kind: LabelKind::Face, 88 + }) 89 + }) 90 + .collect::<Result<Vec<_>, _>>()?; 91 + let edges = ordered_edges(solid) 92 + .into_iter() 93 + .map(|id| { 94 + let label = labeling 95 + .edges 96 + .get(&id) 97 + .copied() 98 + .ok_or(BrepError::MissingLabel { 99 + kind: LabelKind::Edge, 100 + })?; 101 + Ok(EdgeReattach { 102 + label, 103 + curve: labeling.edge_curves.get(&id).cloned(), 104 + }) 105 + }) 106 + .collect::<Result<Vec<_>, BrepError>>()?; 107 + let vertices = ordered_vertices(solid) 108 + .into_iter() 109 + .map(|id| { 110 + labeling 111 + .vertices 112 + .get(&id) 113 + .copied() 114 + .ok_or(BrepError::MissingLabel { 115 + kind: LabelKind::Vertex, 116 + }) 117 + }) 118 + .collect::<Result<Vec<_>, _>>()?; 119 + let mut closed_curves: Vec<SketchEntityId> = labeling.closed_curves.iter().copied().collect(); 120 + closed_curves.sort_unstable(); 121 + Ok(BrepReattach { 122 + faces, 123 + edges, 124 + vertices, 125 + closed_curves, 126 + }) 127 + } 128 + 129 + pub(super) fn to_labeling( 130 + solid: &Solid, 131 + reattach: &BrepReattach, 132 + ) -> Result<SolidLabeling, BrepError> { 133 + let face_ids = ordered_faces(solid); 134 + let edge_ids = ordered_edges(solid); 135 + let vertex_ids = ordered_vertices(solid); 136 + expect_count(LabelKind::Face, face_ids.len(), reattach.faces.len())?; 137 + expect_count(LabelKind::Edge, edge_ids.len(), reattach.edges.len())?; 138 + expect_count(LabelKind::Vertex, vertex_ids.len(), reattach.vertices.len())?; 139 + 140 + let faces = face_ids 141 + .iter() 142 + .zip(&reattach.faces) 143 + .map(|(id, label)| (*id, *label)) 144 + .collect(); 145 + let edges = edge_ids 146 + .iter() 147 + .zip(&reattach.edges) 148 + .map(|(id, edge)| (*id, edge.label)) 149 + .collect(); 150 + let edge_curves = edge_ids 151 + .iter() 152 + .zip(&reattach.edges) 153 + .filter_map(|(id, edge)| edge.curve.clone().map(|curve| (*id, curve))) 154 + .collect(); 155 + let vertices = vertex_ids 156 + .iter() 157 + .zip(&reattach.vertices) 158 + .map(|(id, label)| (*id, *label)) 159 + .collect(); 160 + let closed_curves = reattach.closed_curves.iter().copied().collect(); 161 + Ok(SolidLabeling { 162 + faces, 163 + edges, 164 + vertices, 165 + closed_curves, 166 + edge_curves, 167 + }) 168 + } 169 + 170 + fn expect_count(kind: LabelKind, found: usize, expected: usize) -> Result<(), BrepError> { 171 + if found == expected { 172 + Ok(()) 173 + } else { 174 + Err(BrepError::ReattachMismatch { 175 + kind, 176 + found, 177 + expected, 178 + }) 179 + } 180 + } 181 + 182 + fn quantize(value: f64) -> String { 183 + let prec = GRID_DECIMALS; 184 + let text = format!("{value:.prec$}"); 185 + if text.bytes().all(|byte| matches!(byte, b'-' | b'0' | b'.')) { 186 + format!("{:.prec$}", 0.0) 187 + } else { 188 + text 189 + } 190 + } 191 + 192 + pub(super) fn content_key(solid: &Solid) -> SolidKey { 193 + let mut vertices: Vec<String> = solid 194 + .vertex_iter() 195 + .map(|vertex| { 196 + let point = vertex.point(); 197 + format!( 198 + "{},{},{}", 199 + quantize(point.x), 200 + quantize(point.y), 201 + quantize(point.z) 202 + ) 203 + }) 204 + .collect(); 205 + vertices.sort_unstable(); 206 + vertices.dedup(); 207 + 208 + let mut edges: Vec<String> = solid 209 + .edge_iter() 210 + .map(|edge| { 211 + let front = edge.front().point(); 212 + let back = edge.back().point(); 213 + let mut ends = [ 214 + format!( 215 + "{},{},{}", 216 + quantize(front.x), 217 + quantize(front.y), 218 + quantize(front.z) 219 + ), 220 + format!( 221 + "{},{},{}", 222 + quantize(back.x), 223 + quantize(back.y), 224 + quantize(back.z) 225 + ), 226 + ]; 227 + ends.sort_unstable(); 228 + ends.join("|") 229 + }) 230 + .collect(); 231 + edges.sort_unstable(); 232 + edges.dedup(); 233 + 234 + let canonical = format!( 235 + "t:{}/{}/{}\nv:{}\n{}\ne:{}\n{}\n", 236 + ordered_faces(solid).len(), 237 + ordered_edges(solid).len(), 238 + ordered_vertices(solid).len(), 239 + vertices.len(), 240 + vertices.join("\n"), 241 + edges.len(), 242 + edges.join("\n") 243 + ); 244 + let digest = blake3::hash(canonical.as_bytes()); 245 + let mut key = [0u8; 16]; 246 + key.copy_from_slice(&digest.as_bytes()[..16]); 247 + SolidKey::from_bytes(key) 248 + } 249 + 250 + impl BrepSolid { 251 + pub fn to_blob(&self) -> Result<Vec<u8>, BrepError> { 252 + blob_options() 253 + .to_string(self.arena.solid()) 254 + .map(String::into_bytes) 255 + .map_err(|_| BrepError::BlobSerialize) 256 + } 257 + 258 + #[must_use] 259 + pub fn reattach_data(&self) -> &BrepReattach { 260 + &self.reattach 261 + } 262 + 263 + #[must_use] 264 + pub fn content_key(&self) -> SolidKey { 265 + content_key(self.arena.solid()) 266 + } 267 + 268 + pub fn from_blob(bytes: &[u8], reattach: &BrepReattach) -> Result<BrepSolid, BrepError> { 269 + let text = core::str::from_utf8(bytes).map_err(|_| BrepError::BlobParse)?; 270 + let solid: Solid = blob_options() 271 + .from_str(text) 272 + .map_err(|_| BrepError::BlobParse)?; 273 + let labeling = to_labeling(&solid, reattach)?; 274 + assemble(solid, &labeling) 275 + } 276 + } 277 + 278 + #[cfg(test)] 279 + mod tests { 280 + use super::quantize; 281 + 282 + #[test] 283 + fn quantize_collapses_sub_grid_magnitudes_to_unsigned_zero() { 284 + assert_eq!(quantize(0.0), "0.000000"); 285 + assert_eq!(quantize(-0.0), "0.000000"); 286 + assert_eq!(quantize(4.0e-7), "0.000000"); 287 + assert_eq!(quantize(-4.0e-7), "0.000000"); 288 + assert_eq!(quantize(-1.0e-12), "0.000000"); 289 + assert_eq!(quantize(4.0e-7), quantize(-4.0e-7)); 290 + } 291 + 292 + #[test] 293 + fn quantize_keeps_signed_values_above_the_grid() { 294 + assert_eq!(quantize(-1.0e-6), "-0.000001"); 295 + assert_eq!(quantize(1.0e-6), "0.000001"); 296 + assert_eq!(quantize(-2.5), "-2.500000"); 297 + } 298 + }
+5 -1
crates/bone-kernel/src/circle3.rs
··· 1 + use bone_types::dimensioned_serde; 1 2 use bone_types::{ 2 3 Aabb3, Angle, ChordHeightTolerance, Length, Parameter, Plane3, Point3, Tolerance, UnitVec3, 3 4 Vec3, 4 5 }; 5 6 use core::f64::consts::TAU; 7 + use serde::{Deserialize, Serialize}; 6 8 use uom::si::angle::radian; 7 9 use uom::si::length::millimeter; 8 10 ··· 11 13 use crate::closest::ClosestPoint3; 12 14 use crate::curve3::{Curve3, Curve3Kind}; 13 15 14 - #[derive(Copy, Clone, Debug, PartialEq)] 16 + #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] 17 + #[serde(deny_unknown_fields)] 15 18 pub struct Circle3 { 16 19 plane: Plane3, 20 + #[serde(with = "dimensioned_serde::length_si")] 17 21 radius: Length, 18 22 } 19 23
+3 -3
crates/bone-kernel/src/lib.rs
··· 27 27 pub use brep::eval::evaluate_extrude; 28 28 pub use brep::profile::{ExtrudeProfile, ProfileEdge, ProfileLoop}; 29 29 pub use brep::{ 30 - BrepEdge, BrepError, BrepFace, BrepLoop, BrepShell, BrepSolid, BrepVertex, EdgeCurve3, 31 - EdgePolyline, EdgePolylines, FaceMesh, LabelKind, MeshError, ProfileDefect, SolidMesh, 32 - TruckGap, 30 + BrepEdge, BrepError, BrepFace, BrepLoop, BrepReattach, BrepShell, BrepSolid, BrepVertex, 31 + EdgeCurve3, EdgePolyline, EdgePolylines, EdgeReattach, FaceMesh, LabelKind, MeshError, 32 + ProfileDefect, SolidMesh, TruckGap, 33 33 }; 34 34 pub use circle2::Circle2; 35 35 pub use circle3::Circle3;
+3 -1
crates/bone-kernel/src/line3.rs
··· 1 1 use bone_types::{Aabb3, ChordHeightTolerance, Length, Parameter, Point3, Tolerance, Vec3}; 2 + use serde::{Deserialize, Serialize}; 2 3 use uom::si::length::millimeter; 3 4 4 5 use crate::KernelError; 5 6 use crate::closest::ClosestPoint3; 6 7 use crate::curve3::{Curve3, Curve3Kind}; 7 8 8 - #[derive(Copy, Clone, Debug, PartialEq)] 9 + #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] 10 + #[serde(deny_unknown_fields)] 9 11 pub struct Line3 { 10 12 start: Point3, 11 13 end: Point3,