feat: preserve adaptive sketch profiles and curved modeling workflows
This commit is contained in:
1 parent
4c4a0449a4
commit
4e595deff2
62 files changed
+7114
-362
No files matched your search
+120
-7
@@ -1,13 +1,17 @@
|
||||
//! Interactive sketch tools: click points on the sketch plane (or pick sketch entities) and the
|
||||
//! tool runs the matching command. Tools are UI state only; the work is done by commands.
|
||||
|
||||
use egui::{Pos2, Shape, Stroke};
|
||||
use egui::{Pos2, Stroke};
|
||||
use serde_json::{Value, json};
|
||||
use solvecraft_engine::geom::Vec2;
|
||||
|
||||
use crate::SolveApp;
|
||||
use crate::viewport::{Hit, Proj, pick, sketch_point_at};
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "sketch_display_preview_tests.rs"]
|
||||
mod display_preview_tests;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Kind {
|
||||
/// Click points; `n` points complete the shape (0 = chain until finished).
|
||||
@@ -350,6 +354,117 @@ pub fn finish(app: &mut SolveApp) {
|
||||
}
|
||||
|
||||
/// Rubber-band preview of the shape being drawn.
|
||||
pub(crate) fn paint_preview_curve(
|
||||
sketch: &solvecraft_engine::sketch::Sketch,
|
||||
plane: &solvecraft_engine::geom::Plane,
|
||||
painter: &egui::Painter,
|
||||
proj: &Proj,
|
||||
stroke: Stroke,
|
||||
) {
|
||||
paint_curve(sketch, 0, plane, painter, proj, stroke, &mut solvecraft_engine::render::SamplingBudget::new(4096, 65_536));
|
||||
}
|
||||
|
||||
pub(crate) fn paint_curve(
|
||||
sketch: &solvecraft_engine::sketch::Sketch,
|
||||
index: usize,
|
||||
plane: &solvecraft_engine::geom::Plane,
|
||||
painter: &egui::Painter,
|
||||
proj: &Proj,
|
||||
stroke: Stroke,
|
||||
budget: &mut solvecraft_engine::render::SamplingBudget,
|
||||
) {
|
||||
for part in crate::sketch_display::curve_with_budget(sketch, index, plane, proj, painter.ctx().pixels_per_point(), budget).parts {
|
||||
let screen: Vec<_> = part.into_iter().filter_map(|point| proj.to_screen(point)).collect();
|
||||
if screen.len() >= 2 {
|
||||
painter.add(egui::Shape::line(screen, stroke));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Temporary analytic geometry uses the same exact span extraction as committed curves.
|
||||
/// It never solves constraints or modifies the document.
|
||||
pub(crate) fn preview_curve(t: &Tool, hover: Vec2, sweep: Option<f64>) -> Option<solvecraft_engine::sketch::Sketch> {
|
||||
use solvecraft_engine::sketch::{CurveKind, Sketch};
|
||||
if !hover.is_finite() || t.pts.len() >= solvecraft_engine::sketch::MAX_SPLINE_POINTS || t.pts.iter().any(|p| !p.0.is_finite()) {
|
||||
return None;
|
||||
}
|
||||
let mut sk = Sketch::new();
|
||||
let pts: Vec<_> = t.pts.iter().map(|p| p.0).collect();
|
||||
match (t.cmd, pts.as_slice()) {
|
||||
("sketch.circle.center" | "sketch.polygon.inscribed" | "sketch.polygon.circumscribed", [c]) => {
|
||||
sk.add_circle(*c, c.dist(hover), None, None).ok()?;
|
||||
}
|
||||
("sketch.circle.two_point", [a]) => {
|
||||
sk.add_circle((*a + hover) * 0.5, a.dist(hover) * 0.5, None, None).ok()?;
|
||||
}
|
||||
("sketch.circle.three_point" | "sketch.arc.three_point", [a, b]) => {
|
||||
let ab = *b - *a;
|
||||
let ah = hover - *a;
|
||||
let d = 2.0 * ab.cross(ah);
|
||||
if !d.is_finite() || d.abs() < 1e-12 {
|
||||
return None;
|
||||
}
|
||||
let centre = *a + Vec2::new(ah.y * ab.len2() - ab.y * ah.len2(), ab.x * ah.len2() - ah.x * ab.len2()) / d;
|
||||
if t.cmd == "sketch.circle.three_point" {
|
||||
sk.add_circle(centre, centre.dist(*a), None, None).ok()?;
|
||||
} else {
|
||||
let angle = |p: Vec2| (p - centre).angle();
|
||||
let span = |p: Vec2| (angle(p) - angle(*a)).rem_euclid(std::f64::consts::TAU);
|
||||
let ccw = span(hover) < span(*b);
|
||||
sk.add_arc(centre, if ccw { *a } else { *b }, if ccw { *b } else { *a }, [None; 3], None).ok()?;
|
||||
if let Some(curve) = sk.curves.first_mut() {
|
||||
curve.reversed = !ccw;
|
||||
}
|
||||
}
|
||||
}
|
||||
("sketch.arc.center_point", [c, a]) => {
|
||||
let start = (*a - *c).angle();
|
||||
let sweep = sweep.unwrap_or_else(|| ((hover - *c).angle() - start).rem_euclid(std::f64::consts::TAU));
|
||||
if !sweep.is_finite() || sweep.abs() <= 1e-9 || sweep.abs() >= std::f64::consts::TAU {
|
||||
return None;
|
||||
}
|
||||
let b = *c + Vec2::from_angle(start + sweep) * c.dist(*a);
|
||||
sk.add_arc(*c, if sweep > 0.0 { *a } else { b }, if sweep > 0.0 { b } else { *a }, [None; 3], None).ok()?;
|
||||
if let Some(curve) = sk.curves.first_mut() {
|
||||
curve.reversed = sweep < 0.0;
|
||||
}
|
||||
}
|
||||
("sketch.ellipse", [c, m]) => {
|
||||
let u = (*m - *c).normalized()?;
|
||||
let d = hover - *c;
|
||||
let k = 1.0 - (d.dot(u) / c.dist(*m)).powi(2);
|
||||
if !k.is_finite() || k <= 1e-12 {
|
||||
return None;
|
||||
}
|
||||
let r = d.cross(u).abs() / k.sqrt();
|
||||
let c = sk.add_point(*c, None).ok()?;
|
||||
let m = sk.add_point(*m, None).ok()?;
|
||||
sk.add_curve(CurveKind::Ellipse { c, m, r }, None).ok()?;
|
||||
}
|
||||
("sketch.spline.fit_point" | "sketch.spline.control_point" | "sketch.spline.control_point_5", points) if !points.is_empty() => {
|
||||
let periodic = t.cmd == "sketch.spline.fit_point" && points.len() >= 3 && points.first() == Some(&hover);
|
||||
let mut indices = points.iter().map(|p| sk.add_point(*p, None).ok()).collect::<Option<Vec<_>>>()?;
|
||||
if periodic {
|
||||
indices.push(*indices.first()?);
|
||||
} else if points.last() != Some(&hover) {
|
||||
indices.push(sk.add_point(hover, None).ok()?);
|
||||
}
|
||||
sk.add_curve(
|
||||
CurveKind::Spline {
|
||||
pts: indices,
|
||||
control: t.cmd != "sketch.spline.fit_point",
|
||||
degree: if t.cmd == "sketch.spline.control_point_5" { 5 } else { 3 },
|
||||
periodic,
|
||||
},
|
||||
None,
|
||||
)
|
||||
.ok()?;
|
||||
}
|
||||
_ => return None,
|
||||
}
|
||||
Some(sk)
|
||||
}
|
||||
|
||||
pub fn preview(app: &SolveApp, t: &Tool, painter: &egui::Painter, proj: &Proj) {
|
||||
if crate::sketch3d::preview(app, t, painter, proj) {
|
||||
return;
|
||||
@@ -377,16 +492,16 @@ pub fn preview(app: &SolveApp, t: &Tool, painter: &egui::Painter, proj: &Proj) {
|
||||
}
|
||||
let Some((h, _)) = t.hover.clone() else { return };
|
||||
let h = crate::sketch_dims::effective(app, t, h);
|
||||
if let Some(curve) = preview_curve(t, h, crate::sketch_dims::locked_value(app, t, "Sweep")) {
|
||||
paint_preview_curve(&curve, &ss.plane, painter, proj, stroke);
|
||||
return;
|
||||
}
|
||||
let pts: Vec<Vec2> = t.pts.iter().map(|p| p.0).collect();
|
||||
let line = |a: Vec2, b: Vec2| {
|
||||
if let (Some(x), Some(y)) = (to(a), to(b)) {
|
||||
painter.line_segment([x, y], stroke);
|
||||
}
|
||||
};
|
||||
let circle = |c: Vec2, r: f64| {
|
||||
let v: Vec<Pos2> = (0..=64).filter_map(|i| to(c + Vec2::from_angle(i as f64 / 64.0 * std::f64::consts::TAU) * r)).collect();
|
||||
painter.add(Shape::line(v, stroke));
|
||||
};
|
||||
match (t.cmd, pts.as_slice()) {
|
||||
("sketch.line", [.., last]) => {
|
||||
line(*last, h);
|
||||
@@ -419,8 +534,6 @@ pub fn preview(app: &SolveApp, t: &Tool, painter: &egui::Painter, proj: &Proj) {
|
||||
line(k[i], k[(i + 1) % 4]);
|
||||
}
|
||||
}
|
||||
("sketch.circle.center" | "sketch.polygon.inscribed" | "sketch.polygon.circumscribed", [c]) => circle(*c, c.dist(h)),
|
||||
("sketch.circle.two_point", [a]) => circle((*a + h) * 0.5, a.dist(h) * 0.5),
|
||||
(_, [a]) => line(*a, h),
|
||||
(_, [a, b]) => {
|
||||
line(*a, *b);
|
||||
|
||||
Reference in new issue
Block a user