Merge upstream profile picks and flush-pad fixes with adaptive identities

This commit is contained in:
bherbruck committed 2026-10-11 09:43:16 -04:00
commit 0167c358bb
15 files changed
+623 -44

No files matched your search

+1 -1
View File
@@ -34,7 +34,7 @@ pub use model::{Constraint, ConstraintKind, Curve, CurveKind, SPoint, Sketch, Sk
pub use offset::OffsetChain;
pub use offset_topology::{OffsetCreated, OffsetRole, OffsetRoleFlags, OffsetRoleKind, OffsetTopology};
pub use periodic::periodic_beziers;
pub use profiles::{Profile, find_drawn_profiles, find_profiles, merge_regions};
pub use profiles::{CurvePick, Profile, distinct_curves, find_drawn_profiles, find_profiles, merge_regions, profile_at_point, profile_by_curves};
pub use shape::{Shape, intersections};
pub use solver::{Hold, SolveReport, SolveStatus, measure_dimension, solve, solve_holding};
pub use text::{MAX_TEXT_CHARS, text_geometry};
+63
View File
@@ -922,6 +922,69 @@ fn find_profiles_bounded(sk: &Sketch, max_edges: usize) -> Vec<Profile> {
out
}
/// Why no single profile is bounded by a list of curves.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CurvePick {
/// No profile's outer loop is made of exactly those curves.
Missing,
/// This many profiles are (e.g. both halves of a circle cut by a line).
Ambiguous(usize),
}
/// The profile picked by the curves around it, by index into `profiles`. A profile matches when
/// its outer loop is made of the listed curves and no others: every listed curve, or a piece of
/// it where other curves cut it, lies on the loop, and every piece of the loop belongs to a
/// listed curve. Order and repeats don't matter. Holes don't take part (islands inside a profile
/// are its holes, as for a profile picked by a point). When several profiles match, one whose
/// loop has exactly the listed pieces (a curve listed twice for two pieces) wins; otherwise the
/// pick is ambiguous, never a guess.
pub fn profile_by_curves(profiles: &[Profile], ids: &[String]) -> Result<usize, CurvePick> {
use std::collections::BTreeSet;
let want: BTreeSet<&str> = ids.iter().map(String::as_str).collect();
if want.is_empty() {
return Err(CurvePick::Missing);
}
let hits: Vec<usize> = profiles
.iter()
.enumerate()
.filter(|(_, p)| p.outer_curves.iter().map(String::as_str).collect::<BTreeSet<_>>() == want)
.map(|(i, _)| i)
.collect();
match hits.as_slice() {
[] => Err(CurvePick::Missing),
[i] => Ok(*i),
_ => {
fn sorted(v: &[String]) -> Vec<&str> {
let mut v: Vec<&str> = v.iter().map(String::as_str).collect();
v.sort_unstable();
v
}
let exact = sorted(ids);
let same: Vec<usize> = hits.iter().copied().filter(|i| profiles.get(*i).is_some_and(|p| sorted(&p.outer_curves) == exact)).collect();
match same.as_slice() {
[i] => Ok(*i),
_ => Err(CurvePick::Ambiguous(hits.len())),
}
}
}
}
/// The innermost profile containing a sketch point, by index into `profiles`.
pub fn profile_at_point(profiles: &[Profile], p: Vec2) -> Option<usize> {
profiles.iter().enumerate().filter(|(_, q)| q.region.contains(p)).min_by(|a, b| a.1.area.total_cmp(&b.1.area)).map(|(i, _)| i)
}
/// The distinct curve ids of a loop, in loop order (a curve cut into pieces is listed once).
pub fn distinct_curves(ids: &[String]) -> Vec<String> {
let mut out: Vec<String> = Vec::with_capacity(ids.len());
for id in ids {
if !out.contains(id) {
out.push(id.clone());
}
}
out
}
fn same_seg(a: &Seg2, b: &Seg2, tol: f64) -> bool {
match (*a, *b) {
(Seg2::Line { a: p, b: q }, Seg2::Line { a: r, b: s }) => p.dist(r) <= tol && q.dist(s) <= tol,