mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-03 02:25:55 +00:00
Blends: a blend face is named by its picked edge, not an edge it took up
A 220 x 11 two-distance chamfer of a tapered plate's bottom front edge (rig fillets/5181) runs up the front face past its straight side edges, l2|start and l6|start, and takes them up: no result edge comes from them. SMLib leaves the blend face untagged, and it was given every blended edge whose two faces lie next to it: the picked edge l1|start and both taken-up side edges. Its name came from the first in edge order, F3:blend:F2:side:l6|F2:start, an edge the user never picked. A blend face now comes from the picked edges it lies between; only a face that lies between no picked edge's faces (a corner's piece over a short edge the corner took up) comes from the taken-up edges it lies between. The chamfer is F3:blend:F2:side:l1|F2:start everywhere on it. Persistent names: a blend face that also lay between the faces of an edge the blend ran over is now named by its picked edge only. There is no alias for the old name (5181's F3:blend:F2:side:l6|F2:start): a saved reference to it is reported lost. Also corrects 5181's "what": the chamfer builds (335399.749), it is not refused. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -691,3 +691,25 @@ fn a_shell_point_pick_moved_off_its_face_reopens_only_that_face() {
|
||||
assert!(solid_at(&s, [100.0, 16.0, 10.0]), "the bottom face stays closed");
|
||||
assert!(!solid_at(&s, [149.0, 25.0, 10.0]), "the end at x = 150 is open");
|
||||
}
|
||||
|
||||
/// A two-distance chamfer of a plate's bottom front edge running 220 up the front face, past the
|
||||
/// 85 mm where the plate's sides start to taper (rig fillets/5181): the chamfer takes up the front
|
||||
/// face's straight side edges too, and lies between their faces, but it is made from the picked
|
||||
/// edge alone. Its one face has the picked edge's name everywhere on it (it was named after the
|
||||
/// left side's edge, l6, the first edge it took up).
|
||||
#[test]
|
||||
fn a_chamfer_running_past_the_side_edges_it_takes_up_is_named_by_its_edge() {
|
||||
let mut s = Session::default();
|
||||
run(&mut s, "sketch.create", json!({"plane": {"origin": [0, 0, 0], "x_dir": [1, 0, 0], "y_dir": [0, 0, 1]}, "name": "Bracket"}));
|
||||
let taper = 30.675635228421154;
|
||||
run(&mut s, "sketch.line", json!({"points": [[-50, 0], [50, 0], [50, 85], [taper, 222.5], [-taper, 222.5], [-50, 85]], "closed": true}));
|
||||
run(&mut s, "sketch.finish", json!({}));
|
||||
run(&mut s, "solid.extrude", json!({"sketch": "Bracket", "distance": 23, "body_name": "Bracket"}));
|
||||
run(&mut s, "solid.chamfer", json!({"edges": [[0, 0, 0]], "distance": 220, "distance2": 11}));
|
||||
let blends: Vec<String> = names(&s, "Bracket").into_iter().map(|(n, _)| n).filter(|n| n.starts_with("F3:blend:")).collect();
|
||||
assert_eq!(blends, vec!["F3:blend:F2:side:l1|F2:start".to_string()]);
|
||||
// On the chamfer's plane, through (x, -11, 0) and (x, 0, 220): below and above z = 85.
|
||||
let on = |z: f64| Vec3::new(0.0, -11.0 * (1.0 - z / 220.0), z);
|
||||
let at = solidcraft_doc::naming::face_names_at(&s.model.state(), &[on(40.0), on(150.0)]);
|
||||
assert_eq!(at, vec![blends[0].clone(), blends[0].clone()]);
|
||||
}
|
||||
|
||||
@@ -362,8 +362,8 @@ mod imp {
|
||||
(Some(o), Some(ro)) => match r.history().map_err(|e| failed(what, "history", e))?.history(&[o], ro) {
|
||||
Ok(mut h) => {
|
||||
let operand = History::identity(body).edges;
|
||||
let replaced = blended(body, edges, &h);
|
||||
solidcraft_smlib::history::blends(&mut h, &operand, &replaced);
|
||||
let taken_up = taken_up(body, edges, &h);
|
||||
solidcraft_smlib::history::blends(&mut h, &operand, edges, &taken_up);
|
||||
Some(with_corners(h))
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -414,10 +414,10 @@ mod imp {
|
||||
h
|
||||
}
|
||||
|
||||
/// The operand's edges the blend replaced: each picked edge, and every edge no result edge
|
||||
/// comes from (one the blend's corners took up), in the operand's edge order.
|
||||
fn blended(body: &Body, picked: &[usize], h: &History) -> Vec<usize> {
|
||||
let mut out: BTreeSet<usize> = picked.iter().copied().collect();
|
||||
/// The operand's edges the blend took up besides the picked ones: every edge no result edge
|
||||
/// comes from (one the blend's corners took up, or one it ran over), in the operand's edge
|
||||
/// order.
|
||||
fn taken_up(body: &Body, picked: &[usize], h: &History) -> Vec<usize> {
|
||||
let kept: BTreeSet<usize> = h
|
||||
.edges
|
||||
.iter()
|
||||
@@ -427,8 +427,7 @@ mod imp {
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
out.extend((0..body.edge_count()).filter(|e| !kept.contains(e)));
|
||||
out.into_iter().collect()
|
||||
(0..body.edge_count()).filter(|e| !kept.contains(e) && !picked.contains(e)).collect()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -202,13 +202,17 @@ impl Raw {
|
||||
}
|
||||
}
|
||||
|
||||
/// A blend's faces (fillet, chamfer) in `h`, the history of a blend of edges `blended` of its
|
||||
/// A blend's faces (fillet, chamfer) in `h`, the history of a blend of edges `picked` of its
|
||||
/// one operand (operand 0), whose own edges `operand` lists (the faces each bounds, as in the
|
||||
/// operand's identity history): SMLib makes a blend face with no tag, so each Generated face is
|
||||
/// given [`FaceOrigin::Edge`] for every blended edge whose two faces both lie under faces next
|
||||
/// to it and no longer meet each other directly. A corner patch between several blends matches
|
||||
/// none of them and stays without origin.
|
||||
pub fn blends(h: &mut History, operand: &[EdgeProv], blended: &[usize]) {
|
||||
/// operand's identity history), which also took up edges `taken_up` (no result edge comes from
|
||||
/// them: a corner's short edge, or an edge the blend ran over): SMLib makes a blend face with no
|
||||
/// tag, so each Generated face is given [`FaceOrigin::Edge`] for every picked edge whose two
|
||||
/// faces both lie under faces next to it and no longer meet each other directly, and only where
|
||||
/// no picked edge is such, every taken-up edge that is. A chamfer running up past the end of the
|
||||
/// face beside it takes up that face's side edge and lies between its faces too: it is made from
|
||||
/// the picked edge alone. A corner patch between several blends matches none of them and stays
|
||||
/// without origin.
|
||||
pub fn blends(h: &mut History, operand: &[EdgeProv], picked: &[usize], taken_up: &[usize]) {
|
||||
let mut next_to: Vec<BTreeSet<FaceOrigin>> = vec![BTreeSet::new(); h.faces.len()];
|
||||
for e in &h.edges {
|
||||
for f in &e.faces {
|
||||
@@ -238,14 +242,18 @@ pub fn blends(h: &mut History, operand: &[EdgeProv], blended: &[usize]) {
|
||||
if f.change != Change::Generated || !f.origins.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let edges = blended.iter().filter(|e| {
|
||||
let under = |e: &&usize| {
|
||||
operand.get(**e).is_some_and(|p| {
|
||||
let [a, b] = p.faces.as_slice() else { return false };
|
||||
let (a, b) = (FaceOrigin::Face { operand: 0, face: *a }, FaceOrigin::Face { operand: 0, face: *b });
|
||||
around.contains(&a) && around.contains(&b) && !meeting.contains(&(a.min(b), a.max(b)))
|
||||
})
|
||||
});
|
||||
f.origins = edges.map(|e| FaceOrigin::Edge { operand: 0, edge: *e }).collect();
|
||||
};
|
||||
let mut edges: Vec<usize> = picked.iter().filter(under).copied().collect();
|
||||
if edges.is_empty() {
|
||||
edges = taken_up.iter().filter(under).copied().collect();
|
||||
}
|
||||
f.origins = edges.into_iter().map(|e| FaceOrigin::Edge { operand: 0, edge: e }).collect();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -96,7 +96,7 @@ fn a_fillet_face_comes_from_its_edge() {
|
||||
let mut h = History { operands: 1, faces, edges: vec![edge(4, 6), edge(5, 6), edge(0, 6), edge(2, 6), edge(0, 4)] };
|
||||
// The operand's own edges: edge 0 between faces 4 and 5, edge 1 between 0 and 4.
|
||||
let operand = vec![edge(4, 5), edge(0, 4)];
|
||||
blends(&mut h, &operand, &[0, 1]);
|
||||
blends(&mut h, &operand, &[0], &[1]);
|
||||
assert_eq!(h.faces[6].origins, vec![FaceOrigin::Edge { operand: 0, edge: 0 }]);
|
||||
assert!(h.faces[..6].iter().all(|f| f.origins.len() == 1));
|
||||
}
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
"sources": [
|
||||
"tangency-labs/solidcraft#5181"
|
||||
],
|
||||
"what": "a 220 x 11 two-distance chamfer along the bottom edge of a 23 mm plate 100 wide whose sides run straight for 85 mm and then taper to 61.4 at 222.5: refused ('runs past the faces beside the edge into other geometry; the largest size that fits is 85 mm')",
|
||||
"what": "a 220 x 11 two-distance chamfer along the bottom edge of a 23 mm plate 100 wide whose sides run straight for 85 mm and then taper to 61.4 at 222.5: it builds, the chamfer plane running on past the straight sides into the tapered ones, which it trims",
|
||||
"source": "analytic",
|
||||
"method": "the plate (profile 100x85 + the trapezoid to 61.351 at 222.5) x 23. The first face is the big face (it faces up most: horizontal, the bottom faces down): 220 up it, 11 into the bottom face. The chamfer plane cuts the wedge 11 (1 - z/220) deep across the full width w(z) (100, then tapering past z = 85): Int_0^220 w(z) 11 (1 - z/220) dz, the tapered sides simply trimmed",
|
||||
"body_count": 1,
|
||||
|
||||
Reference in New Issue
Block a user