feat(grips): improve direct editing
Refit arcs from fixed control points and highlight hovered grips. Let Lengthen preview from the cursor or accept a typed distance.
This commit is contained in:
parent
b744d4bf2d
commit
539d83de47
10 changed files with 421 additions and 59 deletions
|
|
@ -85,9 +85,9 @@ pub struct GripPopup {
|
|||
}
|
||||
|
||||
/// Pending follow-up value for grip-menu actions that need a number
|
||||
/// (Lengthen / Radius / Arc Length / Rotate Text). The next number
|
||||
/// typed in the command line is parsed and routed into
|
||||
/// `apply_grip_menu_value` for `(handle, grip_id, action)`.
|
||||
/// (Lengthen / Radius / Arc Length / Rotate Text). Lengthen can also resolve
|
||||
/// from a viewport point; otherwise the next typed number is parsed and routed
|
||||
/// into `apply_grip_menu_value` for `(handle, grip_id, action)`.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct GripPendingValue {
|
||||
pub handle: acadrust::Handle,
|
||||
|
|
|
|||
|
|
@ -9,7 +9,9 @@ use crate::app::helpers::{
|
|||
};
|
||||
use crate::app::{Message, OpenCADStudio, POLY_START_DELAY_MS};
|
||||
use crate::modules::ModuleEvent;
|
||||
use crate::scene::pick::grip::{find_hit_grip, find_hit_grip_paper, find_hit_grip_rte, GripEdit};
|
||||
use crate::scene::pick::grip::{
|
||||
find_hit_grip, find_hit_grip_paper, find_hit_grip_rte, GripEdit, GripEditMode,
|
||||
};
|
||||
use crate::scene::model::object::GripApply;
|
||||
use crate::scene::{
|
||||
self, hover_id, CubeRegion, Scene, VIEWCUBE_DRAW_PX, VIEWCUBE_PAD, VIEWCUBE_PX,
|
||||
|
|
@ -242,9 +244,21 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
|||
"{}: expected a number, got \"{raw}\"",
|
||||
pending.label
|
||||
).as_ref());
|
||||
return Task::none();
|
||||
self.grip_pending = Some(pending);
|
||||
return self.focus_cmd_input();
|
||||
};
|
||||
let i = self.active_tab;
|
||||
let interactive_lengthen = self.tabs[i]
|
||||
.active_grip
|
||||
.as_ref()
|
||||
.is_some_and(|grip| {
|
||||
grip.mode == GripEditMode::Lengthen
|
||||
&& grip.handle == pending.handle
|
||||
&& grip.grip_id == pending.grip_id
|
||||
});
|
||||
if interactive_lengthen {
|
||||
self.cancel_active_grip_edit();
|
||||
}
|
||||
use crate::entities::traits::EntityTypeOps;
|
||||
self.push_undo_snapshot(i, pending.label);
|
||||
if let Some(entity) = self.tabs[i].scene.document.get_entity_mut(pending.handle)
|
||||
|
|
@ -576,6 +590,13 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
|||
if self.visibility_popup.take().is_some() {
|
||||
return Task::none();
|
||||
}
|
||||
if self.tabs[self.active_tab].active_grip.is_some() {
|
||||
self.grip_pending = None;
|
||||
self.command_line.input.clear();
|
||||
if self.cancel_active_grip_edit() {
|
||||
return Task::none();
|
||||
}
|
||||
}
|
||||
if self.grip_pending.take().is_some() {
|
||||
self.command_line.input.clear();
|
||||
return Task::none();
|
||||
|
|
@ -987,7 +1008,26 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
|||
action: item.action,
|
||||
label,
|
||||
});
|
||||
self.command_line.push_info(crate::tf!("{label}:").as_ref());
|
||||
if matches!(item.action, GripMenuAction::Lengthen) {
|
||||
if let Some((_, grip)) = self.tabs[i]
|
||||
.selected_grip_handles
|
||||
.iter()
|
||||
.zip(self.tabs[i].selected_grips.iter())
|
||||
.find(|(owner, grip)| {
|
||||
**owner == popup.handle && grip.id == popup.grip_id
|
||||
})
|
||||
{
|
||||
self.tabs[i].active_grip = Some(GripEdit::lengthen(
|
||||
popup.handle,
|
||||
popup.grip_id,
|
||||
grip.world,
|
||||
));
|
||||
}
|
||||
self.command_line
|
||||
.push_info(crate::t!("Specify point or enter distance:").as_ref());
|
||||
} else {
|
||||
self.command_line.push_info(crate::tf!("{label}:").as_ref());
|
||||
}
|
||||
return self.focus_cmd_input();
|
||||
}
|
||||
// Break at vertex replaces the entity with the split pieces —
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ use crate::app::{Message, OpenCADStudio, POLY_START_DELAY_MS};
|
|||
use crate::modules::ModuleEvent;
|
||||
use crate::scene::model::object::GripApply;
|
||||
use crate::scene::pick::grip::{
|
||||
find_hit_grip, find_hit_grip_paper, find_hit_grip_rte, GripEdit, GripTarget,
|
||||
find_hit_grip, find_hit_grip_paper, find_hit_grip_rte, GripEdit, GripEditMode, GripTarget,
|
||||
};
|
||||
use crate::scene::{
|
||||
self, hover_id, CubeRegion, Scene, VIEWCUBE_DRAW_PX, VIEWCUBE_PAD, VIEWCUBE_PX,
|
||||
|
|
@ -344,6 +344,7 @@ impl OpenCADStudio {
|
|||
grip_id,
|
||||
origin_world: world,
|
||||
last_world: world,
|
||||
mode: GripEditMode::Stretch,
|
||||
targets,
|
||||
}
|
||||
}
|
||||
|
|
@ -1003,18 +1004,91 @@ impl OpenCADStudio {
|
|||
let snap_ms = snap_started.elapsed().as_secs_f64() * 1000.0;
|
||||
let apply_started = Instant::now();
|
||||
let delta = snapped - grip.last_world;
|
||||
let actions: Vec<_> = grip
|
||||
.targets
|
||||
.iter()
|
||||
.map(|target| {
|
||||
let apply = if target.is_translate {
|
||||
GripApply::Translate(delta)
|
||||
} else {
|
||||
GripApply::Absolute(target.last_world + delta)
|
||||
};
|
||||
(target.handle, target.grip_id, apply)
|
||||
})
|
||||
.collect();
|
||||
let lengthen = grip.mode == GripEditMode::Lengthen;
|
||||
let actions: Vec<_> = if lengthen {
|
||||
Vec::new()
|
||||
} else {
|
||||
grip.targets
|
||||
.iter()
|
||||
.map(|target| {
|
||||
let apply = if target.is_translate {
|
||||
GripApply::Translate(delta)
|
||||
} else {
|
||||
GripApply::Absolute(target.last_world + delta)
|
||||
};
|
||||
(target.handle, target.grip_id, apply)
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
if lengthen {
|
||||
let original = self
|
||||
.grip_originals
|
||||
.iter()
|
||||
.find(|(handle, _)| *handle == grip.handle)
|
||||
.map(|(_, entity)| entity.clone());
|
||||
if let Some(original) = original {
|
||||
let action = crate::scene::model::object::GripMenuAction::Lengthen;
|
||||
let value = crate::scene::view::dispatch::grip_menu_point_value(
|
||||
&original,
|
||||
grip.grip_id,
|
||||
action,
|
||||
snapped,
|
||||
);
|
||||
if let Some(value) = value {
|
||||
if let Some(current) =
|
||||
self.tabs[i].scene.document.get_entity_mut(grip.handle)
|
||||
{
|
||||
*current = original;
|
||||
crate::entities::traits::EntityTypeOps::apply_grip_menu_value(
|
||||
current,
|
||||
grip.grip_id,
|
||||
action,
|
||||
value,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Arc point grips are coupled: moving one point must rebuild the
|
||||
// circle from the drag-start start/middle/end set, otherwise the
|
||||
// supposedly fixed points drift a little on every mouse event.
|
||||
let mut arc_grip_edits: rustc_hash::FxHashMap<
|
||||
Handle,
|
||||
Vec<(usize, glam::DVec3)>,
|
||||
> = rustc_hash::FxHashMap::default();
|
||||
for (handle, grip_id, apply) in &actions {
|
||||
if let GripApply::Absolute(point) = apply {
|
||||
if (1..=3).contains(grip_id) {
|
||||
arc_grip_edits
|
||||
.entry(*handle)
|
||||
.or_default()
|
||||
.push((*grip_id, *point));
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut rebuilt_arcs = rustc_hash::FxHashSet::default();
|
||||
for (handle, edits) in arc_grip_edits {
|
||||
let original = self
|
||||
.grip_originals
|
||||
.iter()
|
||||
.find(|(original_handle, _)| *original_handle == handle)
|
||||
.map(|(_, entity)| entity.clone());
|
||||
let Some(original) = original else {
|
||||
continue;
|
||||
};
|
||||
let Some(current) = self.tabs[i].scene.document.get_entity_mut(handle) else {
|
||||
continue;
|
||||
};
|
||||
if crate::scene::view::dispatch::refit_arc_grips(
|
||||
current,
|
||||
&original,
|
||||
&edits,
|
||||
)
|
||||
.is_some()
|
||||
{
|
||||
rebuilt_arcs.insert(handle);
|
||||
}
|
||||
}
|
||||
let refit_arc_targets: Vec<_> = grip
|
||||
.targets
|
||||
.iter()
|
||||
|
|
@ -1033,7 +1107,9 @@ impl OpenCADStudio {
|
|||
})
|
||||
.collect();
|
||||
for (handle, grip_id, apply) in actions {
|
||||
self.tabs[i].scene.apply_grip(handle, grip_id, apply);
|
||||
if !rebuilt_arcs.contains(&handle) {
|
||||
self.tabs[i].scene.apply_grip(handle, grip_id, apply);
|
||||
}
|
||||
}
|
||||
for (handle, vertex_id, original_bulge) in refit_arc_targets {
|
||||
if let Some(entity) = self.tabs[i].scene.document.get_entity_mut(handle) {
|
||||
|
|
@ -2334,6 +2410,10 @@ impl OpenCADStudio {
|
|||
// Engaging click — stay hot, wait for the placement click.
|
||||
return Task::none();
|
||||
}
|
||||
if grip.mode == GripEditMode::Lengthen {
|
||||
self.grip_pending = None;
|
||||
self.command_line.input.clear();
|
||||
}
|
||||
let added_vertex_focus = grip.targets.iter().find_map(|target| {
|
||||
let original = self
|
||||
.grip_originals
|
||||
|
|
|
|||
|
|
@ -451,10 +451,16 @@ impl OpenCADStudio {
|
|||
|| (Some(handle) == sel_h
|
||||
&& Some(grip_id) == current_vertex_grip)
|
||||
});
|
||||
let is_hovered = owner.is_some_and(|handle| {
|
||||
self.grip_hover.as_ref().is_some_and(|hover| {
|
||||
hover.handle == handle && hover.grip_id == grip_id
|
||||
})
|
||||
});
|
||||
crate::ui::overlay::GripMarker {
|
||||
pos: screen,
|
||||
shape,
|
||||
is_hot,
|
||||
is_hovered,
|
||||
dir,
|
||||
}
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in a new issue