Another project
1use std::collections::BTreeSet;
2
3use bone_kernel::{
4 BrepFace, ExtrudeDirection, ExtrudeEndCondition, ExtrudeFeature, ExtrudeSense, MergeResult,
5};
6use bone_types::{
7 ExtrudeId, FaceFingerprint, FaceLabel, FaceRef, FaceRole, FeatureId, Length, Plane3, Point2,
8 Point3, PositiveLength, Resolution, RollbackMarker, SketchDimensionId, SketchEntityId,
9 SketchId, SketchPlaneBasis, Tolerance, UnitVec3, millimeter,
10};
11
12use crate::Sketch;
13use crate::document::Document;
14use crate::matcher::resolve_face;
15use crate::recompute::{EvaluatedModel, RecomputeScope};
16use crate::sketch::{DimensionKind, EditOutcome, SketchDimension, SketchEdit, SketchEntity};
17
18pub(crate) fn xy_basis() -> SketchPlaneBasis {
19 let Ok(basis) = SketchPlaneBasis::new(
20 Point3::origin(),
21 UnitVec3::x_axis(),
22 UnitVec3::y_axis(),
23 Tolerance::new(1e-9),
24 ) else {
25 panic!("xy plane basis is orthogonal");
26 };
27 basis
28}
29
30pub(crate) fn add_point(sketch: Sketch, x: f64, y: f64) -> (Sketch, SketchEntityId) {
31 let Ok((next, EditOutcome::Entity(id))) = sketch.apply(SketchEdit::AddEntity(
32 SketchEntity::point(Point2::from_mm(x, y)),
33 )) else {
34 panic!("add point");
35 };
36 (next, id)
37}
38
39fn circle_entities(radius_mm: f64) -> (Sketch, SketchEntityId) {
40 let (sketch, center) = add_point(Sketch::new(xy_basis()), 0.0, 0.0);
41 let Ok((next, EditOutcome::Entity(circle))) = sketch.apply(SketchEdit::AddEntity(
42 SketchEntity::circle(center, Length::new::<millimeter>(radius_mm), false),
43 )) else {
44 panic!("add circle");
45 };
46 (next, circle)
47}
48
49pub(crate) fn circle_sketch(radius_mm: f64) -> Sketch {
50 circle_entities(radius_mm).0
51}
52
53pub(crate) fn circle_with_radius_dim(
54 radius_mm: f64,
55) -> (Sketch, SketchEntityId, SketchDimensionId) {
56 let (sketch, circle) = circle_entities(radius_mm);
57 let Ok((next, EditOutcome::Dimension(dim))) =
58 sketch.apply(SketchEdit::AddDimension(SketchDimension::Radius {
59 target: circle,
60 value: Length::new::<millimeter>(radius_mm),
61 kind: DimensionKind::Driving,
62 }))
63 else {
64 panic!("add radius dimension");
65 };
66 (next, circle, dim)
67}
68
69pub(crate) fn open_chain_sketch() -> Sketch {
70 let add_line = |sketch: Sketch, from: SketchEntityId, to: SketchEntityId| {
71 let Ok((next, _)) =
72 sketch.apply(SketchEdit::AddEntity(SketchEntity::line(from, to, false)))
73 else {
74 panic!("add line");
75 };
76 next
77 };
78 let (sketch, a) = add_point(Sketch::new(xy_basis()), 0.0, 0.0);
79 let (sketch, b) = add_point(sketch, 10.0, 0.0);
80 let (sketch, c) = add_point(sketch, 10.0, 5.0);
81 add_line(add_line(sketch, a, b), b, c)
82}
83
84pub(crate) fn blind_extrude(sketch: SketchId, depth_mm: f64) -> ExtrudeFeature {
85 let Ok(depth) = PositiveLength::new(Length::new::<millimeter>(depth_mm)) else {
86 panic!("{depth_mm} mm is a positive depth");
87 };
88 ExtrudeFeature {
89 sketch,
90 direction: ExtrudeDirection::Normal {
91 sense: ExtrudeSense::Forward,
92 },
93 end_condition: ExtrudeEndCondition::Blind { depth },
94 draft: None,
95 thin_wall: None,
96 merge_result: MergeResult::Separate,
97 }
98}
99
100pub(crate) struct Chain {
101 pub(crate) sketch: FeatureId,
102 pub(crate) extrude: FeatureId,
103 pub(crate) sketch_id: SketchId,
104 pub(crate) extrude_id: ExtrudeId,
105}
106
107pub(crate) fn chain_handles(
108 document: &Document,
109 sketch_id: SketchId,
110 extrude_id: ExtrudeId,
111) -> Chain {
112 let tree = document.feature_tree();
113 let (Some(sketch), Some(extrude)) = (
114 tree.feature_of_sketch(sketch_id),
115 tree.feature_of_extrude(extrude_id),
116 ) else {
117 panic!("inserted chain resolves to feature ids");
118 };
119 Chain {
120 sketch,
121 extrude,
122 sketch_id,
123 extrude_id,
124 }
125}
126
127pub(crate) fn push_chain(
128 document: &mut Document,
129 label: &str,
130 radius_mm: f64,
131 depth_mm: f64,
132) -> Chain {
133 let sketch_id = document.allocate_sketch();
134 document.insert_sketch(sketch_id, label.to_owned(), circle_sketch(radius_mm));
135 let extrude_id = document.commit_extrude(blind_extrude(sketch_id, depth_mm));
136 chain_handles(document, sketch_id, extrude_id)
137}
138
139pub(crate) fn placeholder_fingerprint() -> FaceFingerprint {
140 FaceFingerprint {
141 plane: Plane3::new_unchecked(Point3::origin(), UnitVec3::x_axis(), UnitVec3::y_axis()),
142 centroid: Point3::origin(),
143 }
144}
145
146fn face_label_of(
147 model: &EvaluatedModel,
148 body: FeatureId,
149 matches_role: impl Fn(FaceRole) -> bool,
150) -> FaceLabel {
151 let Some(solid) = model.body(body) else {
152 panic!("the body is built");
153 };
154 let Some(label) = solid
155 .iter_faces()
156 .map(BrepFace::label)
157 .find(|label| matches_role(label.role))
158 else {
159 panic!("the body carries the requested face role");
160 };
161 label
162}
163
164pub(crate) fn end_cap_ref(model: &EvaluatedModel, body: FeatureId) -> FaceRef {
165 let label = face_label_of(model, body, |role| matches!(role, FaceRole::EndCap));
166 let Resolution::Resolved(resolved) = resolve_face(model, label, placeholder_fingerprint())
167 else {
168 panic!("the end cap resolves on the model that built it");
169 };
170 let Some(face_ref) = model.face_ref(resolved) else {
171 panic!("a planar cap yields a face reference");
172 };
173 face_ref
174}
175
176pub(crate) fn side_face_label(model: &EvaluatedModel, body: FeatureId) -> FaceLabel {
177 face_label_of(model, body, |role| matches!(role, FaceRole::Side { .. }))
178}
179
180pub(crate) fn push_face_bound_chain(
181 document: &mut Document,
182 label: &str,
183 face: FaceRef,
184 radius_mm: f64,
185 depth_mm: f64,
186) -> Chain {
187 let sketch_id = document.allocate_sketch();
188 document.insert_sketch(sketch_id, label.to_owned(), circle_sketch(radius_mm));
189 let Ok(()) = document.bind_sketch_to_face(sketch_id, face) else {
190 panic!("the face binding is acyclic");
191 };
192 let extrude_id = document.commit_extrude(blind_extrude(sketch_id, depth_mm));
193 chain_handles(document, sketch_id, extrude_id)
194}
195
196pub(crate) fn full_model(document: &Document) -> EvaluatedModel {
197 let mut model = EvaluatedModel::new();
198 model.recompute(
199 document,
200 &BTreeSet::new(),
201 RollbackMarker::AtEnd,
202 RecomputeScope::Full,
203 );
204 model
205}