forked from 1b-agent/opencadstudio-upstream
Pick spline control vertices with the configured screen aperture
This commit is contained in:
1 parent
12c661d118
commit
42439c2e50
4 files changed
+74
-10
No files matched your search
@@ -312,6 +312,33 @@ impl OpenCADStudio {
|
|||||||
true
|
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
|
/// Drive the active command's step machine with one [`StepInput`], then
|
||||||
/// apply the result. This is the single entry point every input source
|
/// apply the result. This is the single entry point every input source
|
||||||
/// (command line, headless, dynamic input, plugin API, viewport) funnels
|
/// (command line, headless, dynamic input, plugin API, viewport) funnels
|
||||||
@@ -414,6 +441,7 @@ impl OpenCADStudio {
|
|||||||
command.inject_selection_entities(entities);
|
command.inject_selection_entities(entities);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if matches!(&input, StepInput::Point(_)) { self.refresh_command_point_pick_context(i); }
|
||||||
let ctrl = self.ctrl_down;
|
let ctrl = self.ctrl_down;
|
||||||
let shift = self.shift_down;
|
let shift = self.shift_down;
|
||||||
let result: Option<CmdResult> = {
|
let result: Option<CmdResult> = {
|
||||||
|
|||||||
@@ -3887,6 +3887,7 @@ impl OpenCADStudio {
|
|||||||
} else if self.tabs[i].active_cmd.as_ref()
|
} else if self.tabs[i].active_cmd.as_ref()
|
||||||
.is_some_and(|command| command.entity_pick_accepts_points())
|
.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))
|
self.tabs[i].active_cmd.as_mut().map(|command| command.on_point(pick_wcs))
|
||||||
} else if self.tabs[i]
|
} else if self.tabs[i]
|
||||||
.active_cmd
|
.active_cmd
|
||||||
@@ -3963,6 +3964,7 @@ impl OpenCADStudio {
|
|||||||
// (LINE tangent to two circles, which needs both). When
|
// (LINE tangent to two circles, which needs both). When
|
||||||
// it does, sync last_point to the command's resolved
|
// it does, sync last_point to the command's resolved
|
||||||
// anchor since it replaced the picked coordinate.
|
// anchor since it replaced the picked coordinate.
|
||||||
|
self.refresh_command_point_pick_context(i);
|
||||||
let handled = self.tabs[i]
|
let handled = self.tabs[i]
|
||||||
.active_cmd
|
.active_cmd
|
||||||
.as_mut()
|
.as_mut()
|
||||||
|
|||||||
@@ -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 {
|
pub trait CadCommand: Send {
|
||||||
/// Keep the layer already carried by entities committed by this command
|
/// Keep the layer already carried by entities committed by this command
|
||||||
/// instead of replacing it with the current drawing layer.
|
/// 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).
|
/// Called when the user left-clicks in the viewport (point pick).
|
||||||
fn on_point(&mut self, pt: DVec3) -> CmdResult;
|
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).
|
/// Called when the user presses Enter (finalize / next option).
|
||||||
fn on_enter(&mut self) -> CmdResult;
|
fn on_enter(&mut self) -> CmdResult;
|
||||||
|
|
||||||
|
|||||||
@@ -47,11 +47,31 @@ pub struct SplineditCommand {
|
|||||||
spline: Option<acadrust::entities::Spline>,
|
spline: Option<acadrust::entities::Spline>,
|
||||||
pending: Option<acadrust::entities::Spline>,
|
pending: Option<acadrust::entities::Spline>,
|
||||||
history: Vec<(acadrust::Handle, acadrust::entities::Spline)>,
|
history: Vec<(acadrust::Handle, acadrust::entities::Spline)>,
|
||||||
|
pick_context: Option<crate::command::PointPickContext>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SplineditCommand {
|
impl SplineditCommand {
|
||||||
pub fn new() -> Self {
|
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 {
|
fn replace(&mut self, spline: acadrust::entities::Spline) -> CmdResult {
|
||||||
@@ -194,25 +214,24 @@ impl CadCommand for SplineditCommand {
|
|||||||
}
|
}
|
||||||
Some(CmdResult::NeedPoint)
|
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 {
|
fn on_point(&mut self, point: DVec3) -> CmdResult {
|
||||||
if !point.is_finite() { return CmdResult::NeedPoint; }
|
if !point.is_finite() { return CmdResult::NeedPoint; }
|
||||||
match self.step {
|
match self.step {
|
||||||
Step::SelectVertex { weight, refine } => {
|
Step::SelectVertex { weight, refine } => {
|
||||||
if let Some(index) = self.spline.as_ref().and_then(|spline| spline.control_points.iter().enumerate()
|
if let Some(index) = self.picked_vertex(point) {
|
||||||
.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)) {
|
|
||||||
self.step = if weight { Step::Weight { index } } else { Step::Move { index, refine } };
|
self.step = if weight { Step::Weight { index } } else { Step::Move { index, refine } };
|
||||||
}
|
}
|
||||||
CmdResult::NeedPoint
|
CmdResult::NeedPoint
|
||||||
}
|
}
|
||||||
Step::Delete => {
|
Step::Delete => {
|
||||||
let Some(source) = self.spline.as_ref() else { return CmdResult::NeedPoint; };
|
let Some(source) = self.spline.as_ref() else { return CmdResult::NeedPoint; };
|
||||||
let Some(index) = source.control_points.iter().enumerate().min_by(|(_, a), (_, b)| {
|
let Some(index) = self.picked_vertex(point) else { return CmdResult::NeedPoint; };
|
||||||
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 controls = source.control_points.iter().map(|point| [point.x, point.y, point.z]).collect();
|
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()] }
|
let weights = if source.weights.is_empty() { vec![1.0; source.control_points.len()] }
|
||||||
else { source.weights.clone() };
|
else { source.weights.clone() };
|
||||||
|
|||||||
Reference in new issue
Block a user