forked from 1b-agent/opencadstudio-upstream
Merge pull request #1188 from Ramox81/pedit-enhancements
This commit is contained in:
commit
7d28cc5256
4 files changed
+910
-226
No files matched your search
+216
-59
@@ -1303,6 +1303,211 @@ impl OpenCADStudio {
|
||||
handles
|
||||
}
|
||||
|
||||
fn apply_pedit_result(
|
||||
&mut self,
|
||||
tab: usize,
|
||||
handle: Handle,
|
||||
op: crate::modules::draw::modify::pedit::PeditOp,
|
||||
) -> Task<Message> {
|
||||
use crate::modules::draw::modify::pedit::{apply_pedit, convert_to_polyline, PeditOp};
|
||||
|
||||
if !matches!(&op, PeditOp::Multiple(_, _) | PeditOp::JoinSelection(_))
|
||||
&& self.reject_locked_edit(tab, handle)
|
||||
{
|
||||
return Task::none();
|
||||
}
|
||||
match &op {
|
||||
PeditOp::JoinSelection(handles) => {
|
||||
let mut available = handles
|
||||
.iter()
|
||||
.filter_map(|handle| {
|
||||
if self.tabs[tab].scene.is_layer_locked(*handle) {
|
||||
return None;
|
||||
}
|
||||
self.tabs[tab]
|
||||
.scene
|
||||
.document
|
||||
.get_entity(*handle)
|
||||
.cloned()
|
||||
.map(|entity| (*handle, entity))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let mut changes = Vec::new();
|
||||
while !available.is_empty() {
|
||||
let (source_handle, source) = available.remove(0);
|
||||
let candidates = available
|
||||
.iter()
|
||||
.map(|(handle, entity)| (*handle, entity))
|
||||
.collect::<Vec<_>>();
|
||||
if let Some((mut result, consumed)) =
|
||||
crate::modules::draw::modify::join::join_to_source(&source, &candidates)
|
||||
{
|
||||
*result.common_mut() = source.common().clone();
|
||||
available.retain(|(handle, _)| !consumed.contains(handle));
|
||||
changes.push((source_handle, result, consumed));
|
||||
}
|
||||
}
|
||||
if !changes.is_empty() {
|
||||
self.push_undo_snapshot(tab, "PEDIT");
|
||||
for (source, replacement, consumed) in changes {
|
||||
self.tabs[tab].scene.erase_entities(&consumed);
|
||||
self.tabs[tab].scene.update_entity(replacement.clone());
|
||||
if let Some(command) = self.tabs[tab].active_cmd.as_mut() {
|
||||
for old in consumed {
|
||||
command.on_entity_replaced(old, &[source]);
|
||||
}
|
||||
command.inject_picked_entity(replacement);
|
||||
}
|
||||
}
|
||||
if let Some(command) = self.tabs[tab].active_cmd.as_mut() {
|
||||
command.on_pedit_applied();
|
||||
}
|
||||
self.tabs[tab].dirty = true;
|
||||
self.refresh_properties();
|
||||
}
|
||||
}
|
||||
PeditOp::Multiple(handles, operation) => {
|
||||
let replacements: Vec<_> = handles
|
||||
.iter()
|
||||
.filter(|handle| !self.tabs[tab].scene.is_layer_locked(**handle))
|
||||
.filter_map(|handle| {
|
||||
let original = self.tabs[tab].scene.document.get_entity(*handle)?;
|
||||
if matches!(operation.as_ref(), PeditOp::ConvertToPolyline) {
|
||||
convert_to_polyline(original)
|
||||
.map(|replacement| (*handle, replacement, true))
|
||||
} else {
|
||||
let mut replacement = original.clone();
|
||||
apply_pedit(&mut replacement, operation)
|
||||
.then_some((*handle, replacement, false))
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
if replacements.is_empty() {
|
||||
return Task::none();
|
||||
}
|
||||
self.push_undo_snapshot(tab, "PEDIT");
|
||||
for (handle, replacement, converted) in replacements {
|
||||
let updated_handle = if converted {
|
||||
self.tabs[tab].scene.erase_entities(&[handle]);
|
||||
let new_handle = self.tabs[tab].scene.add_entity(replacement);
|
||||
if let Some(command) = self.tabs[tab].active_cmd.as_mut() {
|
||||
command.on_entity_replaced(handle, &[new_handle]);
|
||||
}
|
||||
new_handle
|
||||
} else {
|
||||
if let Some(entity) = self.tabs[tab].scene.document.get_entity_mut(handle) {
|
||||
*entity = replacement;
|
||||
}
|
||||
self.tabs[tab].scene.refresh_fill_model(handle);
|
||||
self.tabs[tab]
|
||||
.scene
|
||||
.bump_entities(&[(handle, crate::scene::ChangeKind::Modified)]);
|
||||
handle
|
||||
};
|
||||
let updated = self.tabs[tab]
|
||||
.scene
|
||||
.document
|
||||
.get_entity(updated_handle)
|
||||
.cloned();
|
||||
if let (Some(command), Some(entity)) =
|
||||
(self.tabs[tab].active_cmd.as_mut(), updated)
|
||||
{
|
||||
command.inject_picked_entity(entity);
|
||||
}
|
||||
}
|
||||
if let Some(command) = self.tabs[tab].active_cmd.as_mut() {
|
||||
command.on_pedit_applied();
|
||||
}
|
||||
self.tabs[tab].dirty = true;
|
||||
self.refresh_properties();
|
||||
}
|
||||
PeditOp::VertexRange { first, last, split } => {
|
||||
let pieces = self.tabs[tab]
|
||||
.scene
|
||||
.document
|
||||
.get_entity(handle)
|
||||
.and_then(|entity| {
|
||||
crate::modules::draw::modify::pedit::edit_vertex_range(
|
||||
entity, *first, *last, *split,
|
||||
)
|
||||
});
|
||||
if let Some(mut pieces) = pieces {
|
||||
self.push_undo_snapshot(tab, "PEDIT");
|
||||
let source = pieces.remove(0);
|
||||
self.tabs[tab].scene.update_entity(source);
|
||||
for mut piece in pieces {
|
||||
piece.common_mut().handle = Handle::NULL;
|
||||
self.tabs[tab].scene.add_entity(piece);
|
||||
}
|
||||
let updated = self.tabs[tab].scene.document.get_entity(handle).cloned();
|
||||
if let Some(command) = self.tabs[tab].active_cmd.as_mut() {
|
||||
if let Some(entity) = updated {
|
||||
command.inject_picked_entity(entity);
|
||||
}
|
||||
command.on_pedit_applied();
|
||||
}
|
||||
self.tabs[tab].dirty = true;
|
||||
self.refresh_properties();
|
||||
} else {
|
||||
self.command_line.push_error("PEDIT: invalid vertex range.");
|
||||
}
|
||||
}
|
||||
PeditOp::ConvertToPolyline => {
|
||||
let converted = self.tabs[tab]
|
||||
.scene
|
||||
.document
|
||||
.get_entity(handle)
|
||||
.and_then(convert_to_polyline);
|
||||
match converted {
|
||||
Some(polyline) => {
|
||||
return self.apply_cmd_result(CmdResult::ReplaceEntity(
|
||||
handle,
|
||||
vec![polyline],
|
||||
));
|
||||
}
|
||||
None => self.command_line.push_error(
|
||||
crate::t!("PEDIT: cannot convert this entity.").as_ref(),
|
||||
),
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
self.push_undo_snapshot(tab, "PEDIT");
|
||||
let changed = self.tabs[tab]
|
||||
.scene
|
||||
.document
|
||||
.get_entity_mut(handle)
|
||||
.map(|entity| apply_pedit(entity, &op))
|
||||
.unwrap_or(false);
|
||||
if changed {
|
||||
let updated = self.tabs[tab].scene.document.get_entity(handle).cloned();
|
||||
if let Some(command) = self.tabs[tab].active_cmd.as_mut() {
|
||||
if let Some(entity) = updated {
|
||||
command.inject_picked_entity(entity);
|
||||
}
|
||||
command.on_pedit_applied();
|
||||
}
|
||||
self.tabs[tab].dirty = true;
|
||||
self.tabs[tab].scene.refresh_fill_model(handle);
|
||||
self.tabs[tab]
|
||||
.scene
|
||||
.bump_entities(&[(handle, crate::scene::ChangeKind::Modified)]);
|
||||
self.command_line
|
||||
.push_output(crate::t!("PEDIT: applied.").as_ref());
|
||||
self.refresh_properties();
|
||||
} else {
|
||||
self.discard_last_undo_entry(tab);
|
||||
self.command_line.push_error(
|
||||
crate::t!("PEDIT: operation not applicable to this entity.").as_ref(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(prompt) = self.tabs[tab].active_cmd.as_ref().map(|command| command.prompt()) {
|
||||
self.command_line.push_info(&prompt);
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
fn apply_cmd_result_inner(&mut self, result: CmdResult) -> Task<Message> {
|
||||
let i = self.active_tab;
|
||||
if let Some(bind) = self.tabs[i].active_cmd.as_ref().and_then(|command| command.nested_copy_bind_setting()) {
|
||||
@@ -2844,8 +3049,12 @@ impl OpenCADStudio {
|
||||
self.tabs[i].scene.invalidate_dim_block_recorded(nh);
|
||||
}
|
||||
}
|
||||
let pedit_entities = if self.tabs[i].active_cmd.as_ref().is_some_and(|command| command.name() == "PEDIT") {
|
||||
new_handles.iter().filter_map(|new| self.tabs[i].scene.document.get_entity(*new).cloned()).collect::<Vec<_>>()
|
||||
} else { Vec::new() };
|
||||
if let Some(cmd) = &mut self.tabs[i].active_cmd {
|
||||
cmd.on_entity_replaced(handle, &new_handles);
|
||||
for entity in pedit_entities { cmd.inject_picked_entity(entity); }
|
||||
}
|
||||
self.tabs[i].dirty = true;
|
||||
let prompt = self.tabs[i].active_cmd.as_ref().map(|c| c.prompt());
|
||||
@@ -3736,65 +3945,7 @@ impl OpenCADStudio {
|
||||
self.tabs[i].scene.clear_preview_wire();
|
||||
self.restore_pre_cmd_tangent();
|
||||
}
|
||||
CmdResult::PeditOp { handle, op } => {
|
||||
if self.reject_locked_edit(i, handle) {
|
||||
return Task::none();
|
||||
}
|
||||
use crate::modules::draw::modify::pedit::{
|
||||
apply_pedit, convert_to_polyline, PeditOp,
|
||||
};
|
||||
match &op {
|
||||
// The convert replaces the entity (new handle).
|
||||
PeditOp::ConvertToPolyline => {
|
||||
let converted = self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.get_entity(handle)
|
||||
.and_then(convert_to_polyline);
|
||||
match converted {
|
||||
Some(pl) => {
|
||||
return self
|
||||
.apply_cmd_result(CmdResult::ReplaceEntity(handle, vec![pl]));
|
||||
}
|
||||
None => self
|
||||
.command_line
|
||||
.push_error(crate::t!("PEDIT: cannot convert this entity.").as_ref()),
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// Snapshot BEFORE the mutation — an after-the-fact
|
||||
// snapshot records the changed state and undo no-ops.
|
||||
self.push_undo_snapshot(i, "PEDIT");
|
||||
let changed = self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.get_entity_mut(handle)
|
||||
.map(|e| apply_pedit(e, &op))
|
||||
.unwrap_or(false);
|
||||
if changed {
|
||||
if let Some(command) = self.tabs[i].active_cmd.as_mut() {
|
||||
command.on_pedit_applied();
|
||||
}
|
||||
self.tabs[i].dirty = true;
|
||||
// Repaint immediately (wide-band fill included).
|
||||
self.tabs[i].scene.refresh_fill_model(handle);
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.bump_entities(&[(handle, crate::scene::ChangeKind::Modified)]);
|
||||
self.command_line.push_output(crate::t!("PEDIT: applied.").as_ref());
|
||||
self.refresh_properties();
|
||||
} else {
|
||||
self.discard_last_undo_entry(i);
|
||||
self.command_line
|
||||
.push_error(crate::t!("PEDIT: operation not applicable to this entity.").as_ref());
|
||||
}
|
||||
}
|
||||
}
|
||||
let prompt = self.tabs[i].active_cmd.as_ref().map(|c| c.prompt());
|
||||
if let Some(p) = prompt {
|
||||
self.command_line.push_info(&p);
|
||||
}
|
||||
}
|
||||
CmdResult::PeditOp { handle, op } => return self.apply_pedit_result(i, handle, op),
|
||||
CmdResult::JoinToSource { source, handles } => {
|
||||
if self.reject_locked_edit(i, source) { return Task::none(); }
|
||||
let joined = self.tabs[i].scene.document.get_entity(source).and_then(|entity| {
|
||||
@@ -5301,6 +5452,12 @@ impl OpenCADStudio {
|
||||
let active = self.tabs[i].active_cmd.take();
|
||||
self.undo_active_tab();
|
||||
self.tabs[i].active_cmd = active;
|
||||
if self.tabs[i].active_cmd.as_ref().is_some_and(|command| command.name() == "PEDIT") {
|
||||
let entities: Vec<_> = self.tabs[i].scene.document.entities().cloned().collect();
|
||||
if let Some(command) = self.tabs[i].active_cmd.as_mut() {
|
||||
for entity in entities { command.inject_picked_entity(entity); }
|
||||
}
|
||||
}
|
||||
let prompt = self.tabs[i].active_cmd.as_ref().map(|c| c.prompt());
|
||||
if let Some(p) = prompt {
|
||||
self.command_line.push_info(&p);
|
||||
|
||||
@@ -297,6 +297,7 @@ impl OpenCADStudio {
|
||||
header.surface_u_density,
|
||||
header.surface_v_density,
|
||||
)
|
||||
.with_entities(self.tabs[i].scene.document.entities().cloned())
|
||||
.with_preselection(&preselected);
|
||||
self.command_line.push_info(&cmd_obj.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(cmd_obj));
|
||||
|
||||
+670
-157
File diff suppressed because it is too large.
Load diff
@@ -20,10 +20,10 @@
|
||||
// Any other entity type is left untouched: the command returns
|
||||
// `CmdResult::NeedPoint` and keeps prompting so nothing is corrupted.
|
||||
|
||||
use crate::t;
|
||||
use acadrust::entities::Spline;
|
||||
use acadrust::{EntityType, Handle};
|
||||
use glam::DVec3;
|
||||
use crate::t;
|
||||
|
||||
use crate::command::{CadCommand, CmdResult};
|
||||
use crate::modules::{IconKind, ModuleEvent, ToolDef};
|
||||
@@ -62,9 +62,7 @@ impl ReverseCommand {
|
||||
Some(EntityType::Line(out))
|
||||
}
|
||||
EntityType::LwPolyline(pl) => Some(EntityType::LwPolyline(reverse_lwpolyline(pl))),
|
||||
EntityType::Polyline2D(pl) => {
|
||||
Some(EntityType::Polyline2D(reverse_polyline2d(pl)))
|
||||
}
|
||||
EntityType::Polyline2D(pl) => Some(EntityType::Polyline2D(reverse_polyline2d(pl))),
|
||||
EntityType::Polyline(pl) => {
|
||||
let mut out = pl.clone();
|
||||
out.vertices.reverse();
|
||||
@@ -76,7 +74,9 @@ impl ReverseCommand {
|
||||
Some(EntityType::Polyline3D(out))
|
||||
}
|
||||
EntityType::Spline(sp) => Some(EntityType::Spline(reverse_spline(sp))),
|
||||
EntityType::Helix(helix) => crate::entities::helix::reversed(helix).map(EntityType::Helix),
|
||||
EntityType::Helix(helix) => {
|
||||
crate::entities::helix::reversed(helix).map(EntityType::Helix)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -154,8 +154,8 @@ fn reverse_polyline2d(pl: &acadrust::entities::Polyline2D) -> acadrust::entities
|
||||
for vertex in &mut out.vertices {
|
||||
let tangent_flag = acadrust::entities::VertexFlags::CURVE_FIT_TANGENT.bits();
|
||||
if vertex.flags.bits() & tangent_flag != 0 {
|
||||
vertex.curve_tangent = (vertex.curve_tangent + std::f64::consts::PI)
|
||||
.rem_euclid(std::f64::consts::TAU);
|
||||
vertex.curve_tangent =
|
||||
(vertex.curve_tangent + std::f64::consts::PI).rem_euclid(std::f64::consts::TAU);
|
||||
}
|
||||
}
|
||||
if pl.is_closed() {
|
||||
@@ -189,8 +189,13 @@ pub(super) fn reverse_spline(sp: &Spline) -> Spline {
|
||||
if let Some(curve) = super::spline_ops::spline_to_nurbs(sp) {
|
||||
out.knots = curve.reversed().knots().to_vec();
|
||||
}
|
||||
out.begin_tangent = acadrust::types::Vector3::new(-sp.end_tangent.x, -sp.end_tangent.y, -sp.end_tangent.z);
|
||||
out.end_tangent = acadrust::types::Vector3::new(-sp.begin_tangent.x, -sp.begin_tangent.y, -sp.begin_tangent.z);
|
||||
out.begin_tangent =
|
||||
acadrust::types::Vector3::new(-sp.end_tangent.x, -sp.end_tangent.y, -sp.end_tangent.z);
|
||||
out.end_tangent = acadrust::types::Vector3::new(
|
||||
-sp.begin_tangent.x,
|
||||
-sp.begin_tangent.y,
|
||||
-sp.begin_tangent.z,
|
||||
);
|
||||
out
|
||||
}
|
||||
|
||||
@@ -265,7 +270,15 @@ mod tests {
|
||||
spline.end_tangent = Vector3::new(4.0, 5.0, 6.0);
|
||||
|
||||
let reversed = reverse_spline(&spline);
|
||||
assert_eq!(reversed.control_points, spline.control_points.iter().rev().cloned().collect::<Vec<_>>());
|
||||
assert_eq!(
|
||||
reversed.control_points,
|
||||
spline
|
||||
.control_points
|
||||
.iter()
|
||||
.rev()
|
||||
.cloned()
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
assert_eq!(reversed.weights, vec![4.0, 3.0, 2.0, 1.0]);
|
||||
assert_eq!(reversed.knots, vec![0.0, 0.0, 0.0, 0.75, 1.0, 1.0, 1.0]);
|
||||
assert_eq!(reversed.begin_tangent, Vector3::new(-4.0, -5.0, -6.0));
|
||||
|
||||
Reference in new issue
Block a user