fix(donut): keep workflow state consistent
This commit is contained in:
parent
4e80afb0da
commit
8bb18562d2
2 changed files with 47 additions and 62 deletions
|
|
@ -16,6 +16,15 @@ use crate::scene::model::wire_model::TangentGeom;
|
|||
const TAU: f64 = std::f64::consts::TAU;
|
||||
const REVCLOUD_BULGE: f64 = 0.5;
|
||||
const MAX_REVCLOUD_VERTICES: usize = 100_000;
|
||||
const WIDTH_EPSILON: f64 = 1.0e-9;
|
||||
|
||||
fn effective_width(width: f64, constant_width: f64) -> f64 {
|
||||
if width > WIDTH_EPSILON {
|
||||
width
|
||||
} else {
|
||||
constant_width
|
||||
}
|
||||
}
|
||||
|
||||
/// Midpoint position on an arc segment defined by its bulge.
|
||||
fn arc_midpoint(p0: [f64; 2], p1: [f64; 2], bulge: f64) -> [f64; 2] {
|
||||
|
|
@ -500,8 +509,8 @@ fn tapered_band_verts(pline: &LwPolyline) -> Option<Vec<([f64; 2], f64, f64, f64
|
|||
.vertices
|
||||
.iter()
|
||||
.map(|v| {
|
||||
let sw = if v.start_width > 1e-9 { v.start_width } else { c };
|
||||
let ew = if v.end_width > 1e-9 { v.end_width } else { c };
|
||||
let sw = effective_width(v.start_width, c);
|
||||
let ew = effective_width(v.end_width, c);
|
||||
([v.location.x, v.location.y], v.bulge, sw, ew)
|
||||
})
|
||||
.collect();
|
||||
|
|
@ -852,23 +861,11 @@ fn properties(pline: &LwPolyline) -> Vec<PropSection> {
|
|||
let v = pline.vertices.get(vi);
|
||||
let vx = v.map_or(0.0, |v| v.location.x);
|
||||
let vy = v.map_or(0.0, |v| v.location.y);
|
||||
// A constant width is the effective width of every segment whose
|
||||
// per-vertex value is not overridden. Showing the raw zero stored on
|
||||
// those vertices made wide polylines (notably rings) claim that their
|
||||
// visible segment width was zero even though the global width was active.
|
||||
let start_w = v.map_or(0.0, |v| {
|
||||
if v.start_width.abs() > 1.0e-12 {
|
||||
v.start_width
|
||||
} else {
|
||||
pline.constant_width
|
||||
}
|
||||
effective_width(v.start_width, pline.constant_width)
|
||||
});
|
||||
let end_w = v.map_or(0.0, |v| {
|
||||
if v.end_width.abs() > 1.0e-12 {
|
||||
v.end_width
|
||||
} else {
|
||||
pline.constant_width
|
||||
}
|
||||
effective_width(v.end_width, pline.constant_width)
|
||||
});
|
||||
let mp = <LwPolyline as crate::entities::traits::MassPropsCalc>::mass_props(pline);
|
||||
let cloud_arc_length = revision_cloud_arc_length(pline);
|
||||
|
|
@ -1217,16 +1214,8 @@ impl crate::entities::traits::Grippable for LwPolyline {
|
|||
new_v.location.x = midpoint[0];
|
||||
new_v.location.y = midpoint[1];
|
||||
new_v.vertex_id = 0;
|
||||
let effective_start = if v0.start_width > 1e-9 {
|
||||
v0.start_width
|
||||
} else {
|
||||
self.constant_width
|
||||
};
|
||||
let effective_end = if v0.end_width > 1e-9 {
|
||||
v0.end_width
|
||||
} else {
|
||||
self.constant_width
|
||||
};
|
||||
let effective_start = effective_width(v0.start_width, self.constant_width);
|
||||
let effective_end = effective_width(v0.end_width, self.constant_width);
|
||||
let middle_width = (effective_start + effective_end) * 0.5;
|
||||
self.vertices[i0].end_width = middle_width;
|
||||
new_v.start_width = middle_width;
|
||||
|
|
@ -1287,7 +1276,6 @@ pub(crate) fn wide_fills(pl: &acadrust::entities::LwPolyline) -> ([f64; 2], Vec<
|
|||
// Feeding the stored width in whole draws every wide polyline twice as wide
|
||||
// as the file asks for, which on a donut (a closed 2-vertex bulge-1 polyline)
|
||||
// shows up as a disc 1.5× its real radius.
|
||||
let hw_const = pl.constant_width as f32 * 0.5;
|
||||
let verts = &pl.vertices;
|
||||
let n = verts.len();
|
||||
if n < 2 {
|
||||
|
|
@ -1299,16 +1287,8 @@ pub(crate) fn wide_fills(pl: &acadrust::entities::LwPolyline) -> ([f64; 2], Vec<
|
|||
for i in 0..seg_count {
|
||||
let v0 = &verts[i];
|
||||
let v1 = &verts[(i + 1) % n];
|
||||
let hw0 = if v0.start_width > 1e-9 {
|
||||
v0.start_width as f32 * 0.5
|
||||
} else {
|
||||
hw_const
|
||||
};
|
||||
let hw1 = if v0.end_width > 1e-9 {
|
||||
v0.end_width as f32 * 0.5
|
||||
} else {
|
||||
hw_const
|
||||
};
|
||||
let hw0 = effective_width(v0.start_width, pl.constant_width) as f32 * 0.5;
|
||||
let hw1 = effective_width(v0.end_width, pl.constant_width) as f32 * 0.5;
|
||||
if hw0 < 1e-6 && hw1 < 1e-6 {
|
||||
continue;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,12 @@
|
|||
// DONUT command — create a filled circular ring (thick LwPolyline).
|
||||
//
|
||||
// A donut is an LwPolyline with:
|
||||
// - 2 vertices at (cx ± r_avg, 0), both with bulge = 1.0 (two 180° CCW arcs)
|
||||
// - constant_width = (outer - inner) / 2
|
||||
// - is_closed = true
|
||||
//
|
||||
// Workflow:
|
||||
// 1. Type inner diameter (or 0 for a filled circle)
|
||||
// 2. Type outer diameter
|
||||
// 3. Click center point(s); Enter to finish
|
||||
// DONUT creates a ring as a closed, constant-width LwPolyline.
|
||||
|
||||
use acadrust::entities::{LwPolyline, LwVertex};
|
||||
use acadrust::EntityType;
|
||||
use cadkernel::geom2d::{Circle as KernelCircle, Curve as KernelCurve};
|
||||
use cadkernel::geom2d::{
|
||||
Circle as KernelCircle, Curve as KernelCurve, Vec2 as KernelVec2,
|
||||
};
|
||||
use glam::DVec3;
|
||||
|
||||
use crate::t;
|
||||
|
||||
use crate::command::{CadCommand, CmdResult, WorkingPlane};
|
||||
|
|
@ -44,8 +37,6 @@ impl DonutCommand {
|
|||
let mut outer_diameter = defaults::get_donut_outer_diameter();
|
||||
if inner_diameter > outer_diameter {
|
||||
std::mem::swap(&mut inner_diameter, &mut outer_diameter);
|
||||
defaults::set_donut_inner_diameter(inner_diameter);
|
||||
defaults::set_donut_outer_diameter(outer_diameter);
|
||||
}
|
||||
Self {
|
||||
state: DonutState::AskInner,
|
||||
|
|
@ -71,8 +62,23 @@ impl DonutCommand {
|
|||
}
|
||||
|
||||
fn point_distance(&self, first: DVec3, second: DVec3) -> f64 {
|
||||
let delta = self.plane.vector_to_local(second - first);
|
||||
delta.x.hypot(delta.y)
|
||||
let first = self.plane.to_local(first);
|
||||
let second = self.plane.to_local(second);
|
||||
KernelVec2::new(first.x, first.y).distance(KernelVec2::new(second.x, second.y))
|
||||
}
|
||||
|
||||
fn project_to_plane(&self, point: DVec3) -> DVec3 {
|
||||
let local = self.plane.to_local(point);
|
||||
self.plane.to_world(DVec3::new(local.x, local.y, 0.0))
|
||||
}
|
||||
}
|
||||
|
||||
fn prompt_with_default(prompt: &str, value: f64) -> String {
|
||||
match prompt.char_indices().next_back() {
|
||||
Some((index, suffix @ (':' | ':'))) => {
|
||||
format!("{} <{value:.4}>{suffix}", &prompt[..index])
|
||||
}
|
||||
_ => format!("{prompt} <{value:.4}>"),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -92,10 +98,9 @@ impl CadCommand for DonutCommand {
|
|||
DonutState::AskInnerSecond(_) => {
|
||||
t!("DONUT Specify second point for inside diameter:").into_owned()
|
||||
}
|
||||
DonutState::AskOuter => format!(
|
||||
"{} <{:.4}>:",
|
||||
t!("DONUT Specify outside diameter:").trim_end_matches(':'),
|
||||
self.outer_r * 2.0
|
||||
DonutState::AskOuter => prompt_with_default(
|
||||
t!("DONUT Specify outside diameter:").as_ref(),
|
||||
self.outer_r * 2.0,
|
||||
),
|
||||
DonutState::AskOuterSecond(_) => {
|
||||
t!("DONUT Specify second point for outside diameter:").into_owned()
|
||||
|
|
@ -147,7 +152,7 @@ impl CadCommand for DonutCommand {
|
|||
fn on_point(&mut self, pt: DVec3) -> CmdResult {
|
||||
match self.state {
|
||||
DonutState::AskInner => {
|
||||
self.state = DonutState::AskInnerSecond(pt);
|
||||
self.state = DonutState::AskInnerSecond(self.project_to_plane(pt));
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
DonutState::AskInnerSecond(first) => {
|
||||
|
|
@ -159,7 +164,7 @@ impl CadCommand for DonutCommand {
|
|||
CmdResult::NeedPoint
|
||||
}
|
||||
DonutState::AskOuter => {
|
||||
self.state = DonutState::AskOuterSecond(pt);
|
||||
self.state = DonutState::AskOuterSecond(self.project_to_plane(pt));
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
DonutState::AskOuterSecond(first) => {
|
||||
|
|
@ -197,9 +202,10 @@ impl CadCommand for DonutCommand {
|
|||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
|
||||
match self.state {
|
||||
DonutState::AskInnerSecond(first) | DonutState::AskOuterSecond(first) => {
|
||||
let point = self.project_to_plane(pt);
|
||||
Some(WireModel::solid_f64(
|
||||
"rubber_band".into(),
|
||||
vec![first.to_array(), pt.to_array()],
|
||||
vec![first.to_array(), point.to_array()],
|
||||
WireModel::CYAN,
|
||||
false,
|
||||
))
|
||||
|
|
@ -257,8 +263,7 @@ fn make_donut(cx: f64, cy: f64, elevation: f64, inner_r: f64, outer_r: f64) -> E
|
|||
p.constant_width = width;
|
||||
p.elevation = elevation;
|
||||
|
||||
// Constant width is stored once on the polyline. Per-segment width fields
|
||||
// stay zero so a later Global width edit controls the whole ring.
|
||||
// Segment widths inherit the polyline's constant width.
|
||||
let mut v0 = LwVertex::new(Vector2::new(cx - r_avg, cy));
|
||||
v0.bulge = 1.0;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue