feat: pick sketch profiles by their bounding curve ids so features keep the same region when parameters move the geometry, matching curves cut into pieces, refusing ambiguous or missing picks instead of taking another region, and storing point picks by curves

This commit is contained in:
CypherNaugh_0x committed 2026-10-11 10:17:25 +02:00
1 parent 22672585c9
commit 848e7aec0e
11 files changed
+342 -60

No files matched your search

+4 -2
View File
@@ -76,8 +76,10 @@ pub enum ProfileSel {
All,
/// Profiles by index in the solved sketch's profile list.
Indices { indices: Vec<usize> },
/// Profiles whose outer loop is made of exactly these curves (order-insensitive); stable
/// across parameter changes.
/// Profiles whose outer loop is made of exactly these curves, or pieces of them where other
/// curves cut them (order and repeats don't matter; see `sketch::profile_by_curves`). Found
/// again on every evaluation, so stable across parameter changes; no match or several is
/// an error, never another profile.
Curves { loops: Vec<Vec<String>> },
/// The profile containing each sketch point.
Points { points: Vec<Vec2> },
+15 -9
View File
@@ -724,14 +724,8 @@ fn select_profiles(ss: &SolvedSketch, sel: &ProfileSel) -> Result<Vec<Region2>>
ProfileSel::Curves { loops } => {
let mut v = Vec::new();
for l in loops {
let mut want: Vec<&String> = l.iter().collect();
want.sort();
let found = ps.iter().find(|p| {
let mut have: Vec<&String> = p.outer_curves.iter().collect();
have.sort();
have == want
});
v.push(found.ok_or_else(|| DocError::Unknown(format!("profile bounded by {l:?} in {}", ss.name)))?.region.clone());
let i = solvecraft_sketch::profile_by_curves(ps, l).map_err(|e| curve_pick_error(e, l, &ss.name))?;
v.push(ps.get(i).ok_or_else(|| DocError::Unknown(format!("profile {i} of {}", ss.name)))?.region.clone());
}
v
}
@@ -739,7 +733,7 @@ fn select_profiles(ss: &SolvedSketch, sel: &ProfileSel) -> Result<Vec<Region2>>
let mut v = Vec::new();
for p in points {
// Innermost profile containing the point.
let found = ps.iter().filter(|q| q.region.contains(*p)).min_by(|a, b| a.area.total_cmp(&b.area));
let found = solvecraft_sketch::profile_at_point(ps, *p).and_then(|i| ps.get(i));
v.push(found.ok_or_else(|| DocError::Unknown(format!("profile at {:?} in {}", [p.x, p.y], ss.name)))?.region.clone());
}
v
@@ -751,6 +745,18 @@ fn select_profiles(ss: &SolvedSketch, sel: &ProfileSel) -> Result<Vec<Region2>>
Ok(out)
}
/// The error for a profile picked by curves that no longer names exactly one profile.
pub fn curve_pick_error(e: solvecraft_sketch::CurvePick, ids: &[String], sketch: &str) -> DocError {
match e {
solvecraft_sketch::CurvePick::Missing => DocError::Invalid(format!(
"no profile of {sketch} is bounded by exactly the curves {ids:?} (a curve was deleted, or another curve now cuts the region)"
)),
solvecraft_sketch::CurvePick::Ambiguous(n) => DocError::Invalid(format!(
"{n} profiles of {sketch} are bounded by exactly the curves {ids:?}; add a curve that tells them apart, or pick by index or point"
)),
}
}
fn new_name(state: &mut ModelState, f: &Feature, k: usize) -> String {
if let Some(n) = f.body_names.get(k)
&& !n.is_empty()
+2 -2
View File
@@ -28,8 +28,8 @@ pub mod sheet;
pub use assembly::{IDENTITY, Mat, Occurrence, apply_point, apply_vector, is_identity, map_point_expr, mat_inverse, mat_mul, rigid};
pub use document::*;
pub use eval::{
EdgeRef, FeatureResult, Model, ModelBody, ModelState, SolvedSketch, ThreadInfo, edge_names_for, edge_refs, parse_metric_thread, pattern_matrices,
state_in_frame, world_state,
EdgeRef, FeatureResult, Model, ModelBody, ModelState, SolvedSketch, ThreadInfo, curve_pick_error, edge_names_for, edge_refs, parse_metric_thread,
pattern_matrices, state_in_frame, world_state,
};
pub use params::{ParamRow, ParamUser, cycles};
pub use solvecraft_kernel as kernel;