forked from 1b-agent/opencadstudio-upstream
fix(polyline): place added vertices live
Keep new vertices in grip placement until click and restore the original entity on Escape. Correct reverse direction and segment metadata across polyline variants.\n\nRefs #610
This commit is contained in:
1 parent
f0c1be0db1
commit
1cf2b700ce
7 files changed
+547
-56
No files matched your search
@@ -56,7 +56,9 @@ impl OpenCADStudio {
|
||||
|
||||
let handles = std::mem::take(&mut self.grip_preview_handles);
|
||||
let originals = std::mem::take(&mut self.grip_originals);
|
||||
let mut changed_handles: rustc_hash::FxHashSet<_> = handles.iter().copied().collect();
|
||||
for (handle, original) in originals {
|
||||
changed_handles.insert(handle);
|
||||
if let Some(entity) = self.tabs[i].scene.document.get_entity_mut(handle) {
|
||||
*entity = original;
|
||||
}
|
||||
@@ -64,7 +66,7 @@ impl OpenCADStudio {
|
||||
for &handle in &handles {
|
||||
self.tabs[i].scene.preview_hidden.remove(&handle);
|
||||
}
|
||||
let changes: Vec<_> = handles
|
||||
let changes: Vec<_> = changed_handles
|
||||
.into_iter()
|
||||
.map(|handle| (handle, crate::scene::ChangeKind::Modified))
|
||||
.collect();
|
||||
|
||||
@@ -136,6 +136,21 @@ impl OpenCADStudio {
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let prop_vertex = if cur_handles.len() == 1 {
|
||||
let vertex_count = self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.get_entity(cur_handles[0])
|
||||
.and_then(|entity| match entity {
|
||||
acadrust::EntityType::LwPolyline(polyline) => Some(polyline.vertices.len()),
|
||||
acadrust::EntityType::Polyline2D(polyline) => Some(polyline.vertices.len()),
|
||||
acadrust::EntityType::Leader(leader) => Some(leader.vertices.len()),
|
||||
_ => None,
|
||||
});
|
||||
vertex_count.map_or(prop_vertex, |count| prop_vertex.min(count.saturating_sub(1)))
|
||||
} else {
|
||||
prop_vertex
|
||||
};
|
||||
let prop_vertex_indicator_active = if cur_handles == prev_handles {
|
||||
self.tabs[i].properties.prop_vertex_indicator_active
|
||||
} else {
|
||||
|
||||
@@ -21,6 +21,49 @@ use acadrust::{EntityType as AcadEntityType, Handle};
|
||||
use iced::time::Instant;
|
||||
use iced::{mouse, Point, Task};
|
||||
|
||||
/// Return `(new vertex grip id, old vertex count)` for polyline Add Vertex.
|
||||
/// LWPolyline segment grips live after the vertex grips, so translate their id
|
||||
/// back to the segment before selecting the inserted vertex.
|
||||
fn polyline_add_vertex_target(entity: &AcadEntityType, grip_id: usize) -> Option<(usize, usize)> {
|
||||
match entity {
|
||||
AcadEntityType::LwPolyline(polyline) => {
|
||||
let n = polyline.vertices.len();
|
||||
if n == 0 {
|
||||
None
|
||||
} else if grip_id < n {
|
||||
Some((grip_id + 1, n))
|
||||
} else {
|
||||
let segment = grip_id - n;
|
||||
let segment_count = if polyline.is_closed {
|
||||
n
|
||||
} else {
|
||||
n.saturating_sub(1)
|
||||
};
|
||||
(segment < segment_count).then_some((segment + 1, n))
|
||||
}
|
||||
}
|
||||
AcadEntityType::Polyline(polyline) if grip_id < polyline.vertices.len() => {
|
||||
Some((grip_id + 1, polyline.vertices.len()))
|
||||
}
|
||||
AcadEntityType::Polyline2D(polyline) if grip_id < polyline.vertices.len() => {
|
||||
Some((grip_id + 1, polyline.vertices.len()))
|
||||
}
|
||||
AcadEntityType::Polyline3D(polyline) if grip_id < polyline.vertices.len() => {
|
||||
Some((grip_id + 1, polyline.vertices.len()))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn polyline_vertex_count(entity: &AcadEntityType) -> Option<usize> {
|
||||
match entity {
|
||||
AcadEntityType::LwPolyline(polyline) => Some(polyline.vertices.len()),
|
||||
AcadEntityType::Polyline(polyline) => Some(polyline.vertices.len()),
|
||||
AcadEntityType::Polyline2D(polyline) => Some(polyline.vertices.len()),
|
||||
AcadEntityType::Polyline3D(polyline) => Some(polyline.vertices.len()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
impl OpenCADStudio {
|
||||
pub(super) fn begin_tab_close_queue(&mut self, tab_ids: Vec<u64>) -> Task<Message> {
|
||||
@@ -966,6 +1009,93 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||
}
|
||||
return Task::none();
|
||||
}
|
||||
// Polyline Add Vertex is an interactive placement, not an
|
||||
// immediate midpoint edit. Seed the undo snapshot before the
|
||||
// provisional vertex exists, then engage its new grip so the
|
||||
// regular snap/ortho/polar preview follows the cursor. One
|
||||
// click commits the whole append+move; Escape restores the
|
||||
// original entity.
|
||||
if matches!(item.action, GripMenuAction::AddVertex) {
|
||||
let placement = self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.get_entity(popup.handle)
|
||||
.cloned()
|
||||
.and_then(|original| {
|
||||
polyline_add_vertex_target(&original, popup.grip_id)
|
||||
.map(|(new_gid, old_len)| (original, new_gid, old_len))
|
||||
});
|
||||
if let Some((original, new_gid, old_len)) = placement {
|
||||
let dirty_before = self.tabs[i].dirty;
|
||||
if let Some(entity) =
|
||||
self.tabs[i].scene.document.get_entity_mut(popup.handle)
|
||||
{
|
||||
entity.apply_grip_menu(popup.grip_id, item.action);
|
||||
}
|
||||
let inserted = self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.get_entity(popup.handle)
|
||||
.and_then(polyline_vertex_count)
|
||||
.is_some_and(|len| len == old_len + 1);
|
||||
if !inserted {
|
||||
if let Some(entity) =
|
||||
self.tabs[i].scene.document.get_entity_mut(popup.handle)
|
||||
{
|
||||
*entity = original;
|
||||
}
|
||||
self.tabs[i].scene.bump_entities(&[
|
||||
(popup.handle, crate::scene::ChangeKind::Modified),
|
||||
]);
|
||||
self.tabs[i].dirty = dirty_before;
|
||||
self.refresh_selected_grips();
|
||||
self.refresh_properties();
|
||||
self.command_line.push_error("Cannot add a vertex here.");
|
||||
return Task::none();
|
||||
}
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.bump_entities(&[(popup.handle, crate::scene::ChangeKind::Modified)]);
|
||||
self.tabs[i].dirty = true;
|
||||
self.refresh_selected_grips();
|
||||
self.refresh_properties();
|
||||
let grip_world = self.tabs[i]
|
||||
.selected_grip_handles
|
||||
.iter()
|
||||
.zip(self.tabs[i].selected_grips.iter())
|
||||
.find(|(owner, grip)| {
|
||||
**owner == popup.handle && grip.id == new_gid
|
||||
})
|
||||
.map(|(_, grip)| grip.world);
|
||||
if let Some(grip_world) = grip_world {
|
||||
self.grip_originals = vec![(popup.handle, original)];
|
||||
self.grip_dirty_before = Some(dirty_before);
|
||||
self.tabs[i].active_grip = Some(GripEdit::single(
|
||||
popup.handle,
|
||||
new_gid,
|
||||
false,
|
||||
grip_world,
|
||||
));
|
||||
self.command_line
|
||||
.push_info("Specify new vertex location:");
|
||||
} else {
|
||||
if let Some(entity) =
|
||||
self.tabs[i].scene.document.get_entity_mut(popup.handle)
|
||||
{
|
||||
*entity = original;
|
||||
}
|
||||
self.tabs[i].scene.bump_entities(&[
|
||||
(popup.handle, crate::scene::ChangeKind::Modified),
|
||||
]);
|
||||
self.tabs[i].dirty = dirty_before;
|
||||
self.refresh_selected_grips();
|
||||
self.refresh_properties();
|
||||
self.command_line
|
||||
.push_error("Cannot place the new vertex.");
|
||||
}
|
||||
return Task::none();
|
||||
}
|
||||
}
|
||||
// One-shot action — apply immediately.
|
||||
self.push_undo_snapshot(i, item.label);
|
||||
// For Add Leader, the new arrow becomes the last grip; remember
|
||||
|
||||
+105
-11
@@ -34,6 +34,51 @@ fn pt_pt_d2(a: Point, b: Point) -> f32 {
|
||||
(a.x - b.x).powi(2) + (a.y - b.y).powi(2)
|
||||
}
|
||||
|
||||
fn is_added_polyline_vertex(
|
||||
original: &AcadEntityType,
|
||||
current: &AcadEntityType,
|
||||
vertex_id: usize,
|
||||
) -> bool {
|
||||
match (original, current) {
|
||||
(AcadEntityType::LwPolyline(before), AcadEntityType::LwPolyline(after)) => {
|
||||
after.vertices.len() == before.vertices.len() + 1
|
||||
&& vertex_id < after.vertices.len()
|
||||
}
|
||||
(AcadEntityType::Polyline2D(before), AcadEntityType::Polyline2D(after)) => {
|
||||
after.vertices.len() == before.vertices.len() + 1
|
||||
&& vertex_id < after.vertices.len()
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the source bulge when `vertex_id` is the provisional point inserted
|
||||
/// into an arc segment. Straight segments stay straight during placement.
|
||||
fn added_arc_bulge(
|
||||
original: &AcadEntityType,
|
||||
current: &AcadEntityType,
|
||||
vertex_id: usize,
|
||||
) -> Option<f64> {
|
||||
if !is_added_polyline_vertex(original, current, vertex_id) {
|
||||
return None;
|
||||
}
|
||||
let prev = vertex_id.checked_sub(1)?;
|
||||
let (n, closed, bulge) = match original {
|
||||
AcadEntityType::LwPolyline(polyline) => (
|
||||
polyline.vertices.len(),
|
||||
polyline.is_closed,
|
||||
polyline.vertices.get(prev)?.bulge,
|
||||
),
|
||||
AcadEntityType::Polyline2D(polyline) => (
|
||||
polyline.vertices.len(),
|
||||
polyline.is_closed(),
|
||||
polyline.vertices.get(prev)?.bulge,
|
||||
),
|
||||
_ => return None,
|
||||
};
|
||||
((closed || prev + 1 < n) && bulge.abs() >= 1e-9).then_some(bulge)
|
||||
}
|
||||
|
||||
/// Squared distance from `p` to the segment `a`–`b`.
|
||||
fn pt_seg_d2(p: Point, a: Point, b: Point) -> f32 {
|
||||
let (vx, vy) = (b.x - a.x, b.y - a.y);
|
||||
@@ -820,17 +865,22 @@ impl OpenCADStudio {
|
||||
for handle in std::mem::take(&mut self.grip_preview_handles) {
|
||||
self.tabs[i].scene.preview_hidden.remove(&handle);
|
||||
}
|
||||
self.grip_originals = edited_handles
|
||||
.iter()
|
||||
.filter_map(|&handle| {
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.get_entity(handle)
|
||||
.cloned()
|
||||
.map(|entity| (handle, entity))
|
||||
})
|
||||
.collect();
|
||||
// Interactive Add Vertex seeds this with the entity from
|
||||
// before insertion so append + placement is one undo step.
|
||||
// Normal grip drags still snapshot their current entities here.
|
||||
if self.grip_originals.is_empty() {
|
||||
self.grip_originals = edited_handles
|
||||
.iter()
|
||||
.filter_map(|&handle| {
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.get_entity(handle)
|
||||
.cloned()
|
||||
.map(|entity| (handle, entity))
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
for &handle in &edited_handles {
|
||||
self.tabs[i].scene.preview_hidden.insert(handle);
|
||||
}
|
||||
@@ -960,9 +1010,35 @@ impl OpenCADStudio {
|
||||
(target.handle, target.grip_id, apply)
|
||||
})
|
||||
.collect();
|
||||
let refit_arc_targets: Vec<_> = grip
|
||||
.targets
|
||||
.iter()
|
||||
.filter_map(|target| {
|
||||
let original = self
|
||||
.grip_originals
|
||||
.iter()
|
||||
.find(|(handle, _)| *handle == target.handle)
|
||||
.map(|(_, entity)| entity)?;
|
||||
let current = self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.get_entity(target.handle)?;
|
||||
added_arc_bulge(original, current, target.grip_id)
|
||||
.map(|bulge| (target.handle, target.grip_id, bulge))
|
||||
})
|
||||
.collect();
|
||||
for (handle, grip_id, apply) in actions {
|
||||
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) {
|
||||
crate::entities::lwpolyline::refit_added_arc_vertex(
|
||||
entity,
|
||||
vertex_id,
|
||||
original_bulge,
|
||||
);
|
||||
}
|
||||
}
|
||||
self.tabs[i].scene.set_preview_hatches(&edited_handles);
|
||||
self.tabs[i].dirty = true;
|
||||
if let Some(active) = self.tabs[i].active_grip.as_mut() {
|
||||
@@ -2253,6 +2329,19 @@ impl OpenCADStudio {
|
||||
// Engaging click — stay hot, wait for the placement click.
|
||||
return Task::none();
|
||||
}
|
||||
let added_vertex_focus = grip.targets.iter().find_map(|target| {
|
||||
let original = self
|
||||
.grip_originals
|
||||
.iter()
|
||||
.find(|(handle, _)| *handle == target.handle)
|
||||
.map(|(_, entity)| entity)?;
|
||||
let current = self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.get_entity(target.handle)?;
|
||||
is_added_polyline_vertex(original, current, target.grip_id)
|
||||
.then_some(target.grip_id)
|
||||
});
|
||||
self.tabs[i].active_grip = None;
|
||||
// Commit the grip drag as one undoable group, then put every
|
||||
// edited entity back into the resident tessellation.
|
||||
@@ -2287,6 +2376,11 @@ impl OpenCADStudio {
|
||||
// Placement confirmed — keep the just-added leader.
|
||||
self.grip_add_provisional = None;
|
||||
self.tabs[i].snap_result = None;
|
||||
if let Some(vertex_id) = added_vertex_focus {
|
||||
self.tabs[i].properties.prop_vertex = vertex_id;
|
||||
self.tabs[i].properties.prop_vertex_indicator_active = true;
|
||||
crate::scene::view::dispatch::set_prop_current_vertex(vertex_id);
|
||||
}
|
||||
self.refresh_properties();
|
||||
return Task::none();
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user