Blends: a picked edge is named as the blend takes it, the rim and not the seam

A chamfer picked at (10, 0, 20) on a cylinder's top rim lies on the vertex where SMLib's
cylinder has its seam. The blend takes the rim there (blend_smlib::resolve: a seam or a smooth
join loses a tie), but the name stored with the feature came from the nearest mesh edge, the
seam (`F2:side:c1`). Once the stem grew, the name found the seam and SMLib refused the chamfer
(join_extrude_from_a_chamfered_top_face). edge_names_for names the edge each pick takes as a
blend takes it (kernel::blend_edges), the nearest edge only where that finds none.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
jyviko
2026-10-28 22:17:05 -04:00
committed by Irakli Kourtis
parent ae5dc2416c
commit 5547cd98ad
3 changed files with 19 additions and 4 deletions
+10 -2
View File
@@ -3226,7 +3226,8 @@ pub fn edge_refs(st: &ModelState, body: &Option<crate::BodyName>, pts: &[Vec3])
}
/// Persistent names of the edges picked at `pts`, each on the body it lies on (see
/// [`picked_bodies`]).
/// [`picked_bodies`]): of the edge a blend takes at each pick (at a vertex where a seam meets a
/// rim, the rim), else of the nearest edge.
pub fn edge_names_for(st: &ModelState, body: &Option<crate::BodyName>, pts: &[Vec3]) -> Vec<String> {
let Some(on) = picked_bodies(st, body, pts) else { return Vec::new() };
let mut out = vec![String::new(); pts.len()];
@@ -3240,7 +3241,14 @@ pub fn edge_names_for(st: &ModelState, body: &Option<crate::BodyName>, pts: &[Ve
// The body's names are worked out once for all its picks.
let idx: Vec<usize> = on.iter().enumerate().filter(|(_, t)| **t == ti).map(|(i, _)| i).collect();
let at: Vec<Vec3> = idx.iter().filter_map(|i| pts.get(*i).copied()).collect();
for (i, n) in idx.into_iter().zip(crate::naming::edge_names_at(b, &at)) {
let names = match kernel::blend_edges(&b.body, &at) {
Ok(edges) => {
let all = crate::naming::edge_names(b);
edges.iter().map(|e| all.get(*e).cloned().unwrap_or_default()).collect()
}
Err(_) => crate::naming::edge_names_at(b, &at),
};
for (i, n) in idx.into_iter().zip(names) {
if let Some(slot) = out.get_mut(i) {
*slot = n;
}
+7
View File
@@ -617,6 +617,13 @@ pub fn chamfer_sides(body: &Body, edges: &[Vec3], d1: f64, side: ChamferSide, fl
crate::blend_smlib::chamfer_sides(body, edges, d1, side, flip)
}
/// The edge each pick takes, as a blend takes it (an index in the body's edge order, the order
/// of its mesh's edges): the nearest, and at a vertex where a seam or a smooth join meets a sharp
/// edge (a cylinder's seam at its rim), the sharp edge. Picks that find no edge are refused.
pub fn blend_edges(body: &Body, picks: &[Vec3]) -> Result<Vec<usize>> {
crate::blend_smlib::picked_edges(body, picks).map(|(edges, _)| edges)
}
/// The picked edges' tangent chains, for a blend that is to run along them: `picks` (each the
/// nearest edge of `body` to it), then a point on every other edge that continues one of them
/// tangentially (within 0.5° at a shared vertex), until no more do. Each pick takes its edge as a
+2 -2
View File
@@ -155,8 +155,8 @@ mod validity;
pub mod watch;
pub use blend::{
ChamferSide, chamfer, chamfer_sides, fillet, fillet_each, fillet_g2, fillet_g2_corners, fillet_least_setback, fillet_max, fillet_max_each,
fillet_setback, tangent_chain,
ChamferSide, blend_edges, chamfer, chamfer_sides, fillet, fillet_each, fillet_g2, fillet_g2_corners, fillet_least_setback, fillet_max,
fillet_max_each, fillet_setback, tangent_chain,
};
pub use blend_corner::fillet_radii;
pub use blend_variable::{RadiusSpline, Taper, fillet_chord, fillet_tapered, fillet_variable, fillet_variable_points, fillet_variable_spline, taper};