forked from 1b-agent/opencadstudio-upstream
feat(blocks): wire Stretch actions and independent reference evaluation
Author a native Stretch action from a Linear, Polar or XY parameter, a two-corner stretch frame, an object selection and a marker. Preview and retain its frame in BEDIT, with selectable and movable action markers. Freeze line and planar lightweight-polyline vertex bindings, preserve curved-segment height, move wholly selected objects, and evaluate independent saved INSERTs from their definition. Retain native DWG/DXF data, copied-reference isolation and undo/redo. Validate with synthetic frame, topology, parameter, history, Test Block, drawing-reference, COPY and roundtrip regressions: 340 block tests pass (6 existing opt-in ignored), plus 41 dock and 27 viewport tests. Other entity types, Polar Stretch, custom multipliers and chaining remain unsupported.
This commit is contained in:
1 parent
94940ad77c
commit
042f26a4cc
14 files changed
+827
-27
No files matched your search
@@ -38,7 +38,7 @@ impl OpenCADStudio {
|
||||
};
|
||||
if !args.is_empty() && kind.is_none() {
|
||||
self.command_line.push_error(
|
||||
crate::t!("Supported actions: Move, Scale, Rotate, Flip. Other action types are not wired yet.")
|
||||
crate::t!("Supported actions: Move, Scale, Stretch, Rotate, Flip. Other action types are not wired yet.")
|
||||
.as_ref(),
|
||||
);
|
||||
return Task::none();
|
||||
@@ -76,6 +76,7 @@ impl OpenCADStudio {
|
||||
parameter,
|
||||
targets,
|
||||
location,
|
||||
frame,
|
||||
} => {
|
||||
if self.tabs[i].id != document
|
||||
|| self.tabs[i]
|
||||
@@ -90,8 +91,15 @@ impl OpenCADStudio {
|
||||
return Task::none();
|
||||
}
|
||||
let mut staged = self.tabs[i].scene.document.clone();
|
||||
match actions::create(&mut staged, definition, kind, parameter, &targets, location)
|
||||
{
|
||||
match actions::create_with_frame(
|
||||
&mut staged,
|
||||
definition,
|
||||
kind,
|
||||
parameter,
|
||||
&targets,
|
||||
location,
|
||||
frame,
|
||||
) {
|
||||
Ok(_) => {
|
||||
self.push_undo_snapshot(i, &format!("{} action", kind.name()));
|
||||
self.tabs[i].scene.document = staged;
|
||||
|
||||
@@ -59,6 +59,138 @@ fn feed(app: &mut OpenCADStudio, text: &str) {
|
||||
let _ = app.update(Message::CommandSubmit);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_actions_stretch_frame_selection_history_test_and_saved_grip() {
|
||||
use crate::scene::block_actions::{self as actions, Kind};
|
||||
let (mut app, reference) = fixture();
|
||||
begin(&mut app, reference);
|
||||
let i = app.active_tab;
|
||||
let def = app.tabs[i].active_block_edit_session().unwrap().br_handle;
|
||||
let _ = app.run_command_line("BPARAMETER LINEAR 0,0 10,0 5,2");
|
||||
let p = actions::parameters(&app.tabs[i].scene.document, def, Kind::Stretch)[0].clone();
|
||||
let before = app.tabs[i].scene.document.clone();
|
||||
// A half-authored frame and Escape create neither objects nor history.
|
||||
let depth = app.tabs[i].history.undo_stack.len();
|
||||
let _ = app.dispatch_command("BACTION Stretch");
|
||||
feed(&mut app, &p.name);
|
||||
feed(&mut app, "0.5,-1");
|
||||
let _ = app.update(Message::CommandEscape);
|
||||
assert_eq!(app.tabs[i].scene.document, before);
|
||||
assert_eq!(app.tabs[i].history.undo_stack.len(), depth);
|
||||
let _ = app.dispatch_command("BACTION Stretch");
|
||||
feed(&mut app, &p.name);
|
||||
assert!(!app.tabs[i]
|
||||
.active_cmd
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.is_selection_gathering());
|
||||
feed(&mut app, "0.5,-1");
|
||||
feed(&mut app, "0.5,1"); // zero-width error retains first corner
|
||||
assert!(!app.tabs[i]
|
||||
.active_cmd
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.is_selection_gathering());
|
||||
feed(&mut app, "2,1");
|
||||
assert!(app.tabs[i]
|
||||
.active_cmd
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.is_selection_gathering());
|
||||
let targets = members(&app);
|
||||
let _ = app.feed_command(crate::command::StepInput::SelectionComplete(
|
||||
targets.clone(),
|
||||
));
|
||||
let _ = app.feed_command(crate::command::StepInput::Enter);
|
||||
feed(&mut app, "3,4");
|
||||
assert!(app.tabs[i].active_cmd.is_none());
|
||||
assert_eq!(app.tabs[i].history.undo_stack.len(), depth + 1);
|
||||
let action = actions::actions(&app.tabs[i].scene.document, def)[0].0;
|
||||
assert_eq!(
|
||||
actions::frame_outline(&app.tabs[i].scene.document, action).len(),
|
||||
4
|
||||
);
|
||||
app.undo_steps(1);
|
||||
assert!(actions::actions(&app.tabs[i].scene.document, def).is_empty());
|
||||
app.redo_steps(1);
|
||||
let editor = app.tabs[i].scene.document.clone();
|
||||
let _ = app.dispatch_command("BTESTBLOCK");
|
||||
let ti = app.active_tab;
|
||||
let test_insert = app.tabs[ti].block_test.as_ref().unwrap().insert;
|
||||
app.tabs[ti].scene.selected.insert(test_insert);
|
||||
app.refresh_properties();
|
||||
let grip = app.tabs[ti]
|
||||
.selected_grips
|
||||
.iter()
|
||||
.find(|g| super::block_actions::is_test_grip(g.id))
|
||||
.unwrap()
|
||||
.id;
|
||||
assert!(app.start_test_action_grip(test_insert, grip));
|
||||
feed(&mut app, "12,0");
|
||||
let EntityType::Line(line) = app.tabs[ti].scene.document.get_entity(targets[0]).unwrap() else {
|
||||
panic!()
|
||||
};
|
||||
assert_eq!(line.start, Vector3::ZERO);
|
||||
assert_eq!(line.end, Vector3::new(3., 0., 0.));
|
||||
let _ = app.dispatch_command("BCLOSETESTBLOCK");
|
||||
assert_eq!(app.tabs[i].scene.document, editor);
|
||||
let _ = app.dispatch_command("BSAVE");
|
||||
let _ = app.dispatch_command("BCLOSE");
|
||||
app.tabs[i].scene.selected.insert(reference);
|
||||
app.refresh_properties();
|
||||
let grip = app.tabs[i]
|
||||
.selected_grips
|
||||
.iter()
|
||||
.find(|g| super::block_actions::is_test_grip(g.id))
|
||||
.unwrap()
|
||||
.id;
|
||||
let depth = app.tabs[i].history.undo_stack.len();
|
||||
assert!(app.start_test_action_grip(reference, grip));
|
||||
feed(&mut app, "15,0");
|
||||
assert_eq!(app.tabs[i].history.undo_stack.len(), depth + 1);
|
||||
let changed = app.tabs[i].scene.document.clone();
|
||||
let EntityType::Insert(insert) = changed.get_entity(reference).unwrap() else {
|
||||
panic!()
|
||||
};
|
||||
let member = changed
|
||||
.block_records
|
||||
.get(&insert.block_name)
|
||||
.unwrap()
|
||||
.entity_handles[0];
|
||||
let EntityType::Line(line) = changed.get_entity(member).unwrap() else {
|
||||
panic!()
|
||||
};
|
||||
assert_eq!(line.start, Vector3::ZERO);
|
||||
assert_eq!(line.end, Vector3::new(6., 0., 0.));
|
||||
assert_eq!(
|
||||
changed.get_entity(targets[0]),
|
||||
editor.get_entity(targets[0])
|
||||
);
|
||||
app.undo_steps(1);
|
||||
assert_eq!(
|
||||
app.tabs[i].scene.document.get_entity(reference),
|
||||
editor.get_entity(reference)
|
||||
);
|
||||
app.redo_steps(1);
|
||||
assert_eq!(
|
||||
app.tabs[i].scene.document.get_entity(member),
|
||||
changed.get_entity(member)
|
||||
);
|
||||
let bytes = codec::DwgWriter::write_to_vec(&changed).unwrap();
|
||||
app.tabs[i].scene.document = codec::DwgReader::from_stream(std::io::Cursor::new(bytes))
|
||||
.read()
|
||||
.unwrap();
|
||||
begin(&mut app, reference);
|
||||
assert_eq!(
|
||||
app.tabs[i].active_block_edit_session().unwrap().br_handle,
|
||||
def
|
||||
);
|
||||
assert_eq!(
|
||||
actions::frame_outline(&app.tabs[i].scene.document, action).len(),
|
||||
4
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_parameters_rotation_only_save_close_reopen_then_flip() {
|
||||
use crate::scene::block_actions::{self as actions, Kind};
|
||||
@@ -903,6 +1035,7 @@ fn block_actions_rejected_outside_editor_preserve_running_command_and_stale_resu
|
||||
parameter: p,
|
||||
targets: members(&app),
|
||||
location: glam::DVec3::ZERO,
|
||||
frame: None,
|
||||
});
|
||||
assert_eq!(app.tabs[i].scene.document, before);
|
||||
let _ = app.dispatch_command("LINE");
|
||||
|
||||
@@ -1244,6 +1244,27 @@ fn block_actions_palette_controls_require_compatible_parameters_and_start_comman
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_actions_stretch_palette_requires_distance_parameter() {
|
||||
with_stack(|| {
|
||||
let mut app = authoring_app();
|
||||
let _ = app.dispatch_command("BAUTHORPALETTE");
|
||||
app.authoring_palette_tab = crate::ui::window::authoring_palettes::Tab::Actions;
|
||||
let point = authoring_control(&app, "authoring-tool-stretch-action");
|
||||
authoring_click(&mut app, point);
|
||||
assert!(app.tabs[app.active_tab].active_cmd.is_none());
|
||||
let _ = app.run_command_line("BPARAMETER LINEAR 0,0 10,0 5,2");
|
||||
let point = authoring_control(&app, "authoring-tool-stretch-action");
|
||||
authoring_click(&mut app, point);
|
||||
assert!(app.tabs[app.active_tab]
|
||||
.active_cmd
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.prompt()
|
||||
.contains("Stretch"));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_actions_rotate_flip_palette_requires_matching_parameters() {
|
||||
with_stack(|| {
|
||||
|
||||
@@ -415,6 +415,7 @@ impl OpenCADStudio {
|
||||
self.action_error(Some(crate::scene::block_actions::Kind::Scale)),
|
||||
self.action_error(Some(crate::scene::block_actions::Kind::Rotate)),
|
||||
self.action_error(Some(crate::scene::block_actions::Kind::Flip)),
|
||||
self.action_error(Some(crate::scene::block_actions::Kind::Stretch)),
|
||||
)
|
||||
}
|
||||
crate::ui::dock::PanelId::BlockPalette => {
|
||||
|
||||
+12
-1
@@ -1203,8 +1203,19 @@ bg={bg_ms:.1}ms n={view_count}"
|
||||
session.br_handle,
|
||||
)
|
||||
.into_iter()
|
||||
.map(|(_, action, _, _)| {
|
||||
.map(|(handle, action, _, _)| {
|
||||
let mut g = crate::scene::block_distance_parameter::Graphic::default();
|
||||
let frame = crate::scene::block_actions::frame_outline(
|
||||
&tab.scene.document,
|
||||
handle,
|
||||
);
|
||||
g.lines.extend(
|
||||
frame
|
||||
.iter()
|
||||
.zip(frame.iter().cycle().skip(1))
|
||||
.take(frame.len())
|
||||
.map(|(a, b)| [*a, *b]),
|
||||
);
|
||||
g.grips.push((action.display_location, false));
|
||||
g.labels
|
||||
.push((action.display_location, action.element.name.clone()));
|
||||
|
||||
Reference in new issue
Block a user