Pick spline control vertices with the configured screen aperture

This commit is contained in:
ramox81 committed 2026-09-11 11:13:09 +03:00
1 parent 12c661d118
commit 42439c2e50
4 files changed
+74 -10

No files matched your search

+28
View File
@@ -312,6 +312,33 @@ impl OpenCADStudio {
true
}
/// Refresh screen-space feature picking immediately before point dispatch.
pub(super) fn refresh_command_point_pick_context(&mut self, i: usize) {
if self.tabs[i].active_cmd.as_ref()
.is_some_and(|command| command.wants_point_pick_context())
{
let scene = &self.tabs[i].scene;
let size = scene.selection.borrow().vp_size;
let edit = scene.viewport_edit_frame(size);
let tile = edit.as_ref().map(|(_, rect)| *rect)
.unwrap_or_else(|| scene.active_model_tile_bounds(size.0, size.1));
let bounds = iced::Rectangle { x: 0.0, y: 0.0, width: tile.width, height: tile.height };
let context = if bounds.width > 0.0 && bounds.height > 0.0 {
let (view, eye) = if let Some((camera, _)) = edit {
(camera.view_proj_rte(bounds), camera.eye())
} else {
let camera = scene.camera.borrow();
(camera.view_proj_rte(bounds), camera.eye())
};
Some(crate::command::PointPickContext { view, eye, bounds,
aperture_px: crate::ui::overlay::pick_box_aperture_px(self.pick_box) })
} else { None };
if let Some(command) = self.tabs[i].active_cmd.as_mut() {
command.set_point_pick_context(context);
}
}
}
/// Drive the active command's step machine with one [`StepInput`], then
/// apply the result. This is the single entry point every input source
/// (command line, headless, dynamic input, plugin API, viewport) funnels
@@ -414,6 +441,7 @@ impl OpenCADStudio {
command.inject_selection_entities(entities);
}
}
if matches!(&input, StepInput::Point(_)) { self.refresh_command_point_pick_context(i); }
let ctrl = self.ctrl_down;
let shift = self.shift_down;
let result: Option<CmdResult> = {
+2
View File
@@ -3887,6 +3887,7 @@ impl OpenCADStudio {
} else if self.tabs[i].active_cmd.as_ref()
.is_some_and(|command| command.entity_pick_accepts_points())
{
self.refresh_command_point_pick_context(i);
self.tabs[i].active_cmd.as_mut().map(|command| command.on_point(pick_wcs))
} else if self.tabs[i]
.active_cmd
@@ -3963,6 +3964,7 @@ impl OpenCADStudio {
// (LINE tangent to two circles, which needs both). When
// it does, sync last_point to the command's resolved
// anchor since it replaced the picked coordinate.
self.refresh_command_point_pick_context(i);
let handled = self.tabs[i]
.active_cmd
.as_mut()
+15
View File
@@ -1939,6 +1939,15 @@ impl InputKind {
}
}
/// Current screen projection and configured aperture for point-feature picking.
#[derive(Clone, Copy)]
pub struct PointPickContext {
pub view: glam::Mat4,
pub eye: DVec3,
pub bounds: iced::Rectangle,
pub aperture_px: f32,
}
pub trait CadCommand: Send {
/// Keep the layer already carried by entities committed by this command
/// instead of replacing it with the current drawing layer.
@@ -2006,6 +2015,12 @@ pub trait CadCommand: Send {
/// Called when the user left-clicks in the viewport (point pick).
fn on_point(&mut self, pt: DVec3) -> CmdResult;
/// Opt in only while a point input selects an existing point feature.
fn wants_point_pick_context(&self) -> bool { false }
/// Refreshed for each point input so zoom and viewport changes are reflected.
fn set_point_pick_context(&mut self, _context: Option<PointPickContext>) {}
/// Called when the user presses Enter (finalize / next option).
fn on_enter(&mut self) -> CmdResult;
+29 -10
View File
@@ -47,11 +47,31 @@ pub struct SplineditCommand {
spline: Option<acadrust::entities::Spline>,
pending: Option<acadrust::entities::Spline>,
history: Vec<(acadrust::Handle, acadrust::entities::Spline)>,
pick_context: Option<crate::command::PointPickContext>,
}
impl SplineditCommand {
pub fn new() -> Self {
Self { step: Step::SelectSpline, handle: acadrust::Handle::NULL, spline: None, pending: None, history: Vec::new() }
Self { step: Step::SelectSpline, handle: acadrust::Handle::NULL, spline: None, pending: None, history: Vec::new(), pick_context: None }
}
fn picked_vertex(&self, point: DVec3) -> Option<usize> {
let context = self.pick_context?;
let project = |point: DVec3| {
let clip = context.view * (point - context.eye).as_vec3().extend(1.0);
if !clip.is_finite() || clip.w <= 0.0 { return None; }
let screen = crate::scene::pick::hit_test::world_to_screen(
point, context.view, context.eye, context.bounds);
(screen.x.is_finite() && screen.y.is_finite()
&& screen.x >= 0.0 && screen.x <= context.bounds.width
&& screen.y >= 0.0 && screen.y <= context.bounds.height).then_some(screen)
};
let cursor = project(point)?;
self.spline.as_ref()?.control_points.iter().enumerate().filter_map(|(index, vertex)| {
let screen = project(DVec3::new(vertex.x, vertex.y, vertex.z))?;
let distance = (screen.x - cursor.x).hypot(screen.y - cursor.y);
(distance.is_finite() && distance <= context.aperture_px).then_some((index, distance))
}).min_by(|a, b| a.1.total_cmp(&b.1)).map(|(index, _)| index)
}
fn replace(&mut self, spline: acadrust::entities::Spline) -> CmdResult {
@@ -194,25 +214,24 @@ impl CadCommand for SplineditCommand {
}
Some(CmdResult::NeedPoint)
}
fn wants_point_pick_context(&self) -> bool {
matches!(self.step, Step::Delete | Step::SelectVertex { .. })
}
fn set_point_pick_context(&mut self, context: Option<crate::command::PointPickContext>) {
self.pick_context = context;
}
fn on_point(&mut self, point: DVec3) -> CmdResult {
if !point.is_finite() { return CmdResult::NeedPoint; }
match self.step {
Step::SelectVertex { weight, refine } => {
if let Some(index) = self.spline.as_ref().and_then(|spline| spline.control_points.iter().enumerate()
.min_by(|(_, a), (_, b)| {
let distance = |p: &Vector3| point.distance_squared(DVec3::new(p.x, p.y, p.z));
distance(a).total_cmp(&distance(b))
}).map(|(index, _)| index)) {
if let Some(index) = self.picked_vertex(point) {
self.step = if weight { Step::Weight { index } } else { Step::Move { index, refine } };
}
CmdResult::NeedPoint
}
Step::Delete => {
let Some(source) = self.spline.as_ref() else { return CmdResult::NeedPoint; };
let Some(index) = source.control_points.iter().enumerate().min_by(|(_, a), (_, b)| {
let distance = |p: &Vector3| point.distance_squared(DVec3::new(p.x, p.y, p.z));
distance(a).total_cmp(&distance(b))
}).map(|(index, _)| index) else { return CmdResult::NeedPoint; };
let Some(index) = self.picked_vertex(point) else { return CmdResult::NeedPoint; };
let controls = source.control_points.iter().map(|point| [point.x, point.y, point.z]).collect();
let weights = if source.weights.is_empty() { vec![1.0; source.control_points.len()] }
else { source.weights.clone() };