Perspective: reapplying the current grid preset adds no undo step

perspective.grid.preset stored the preset's grid every time, so applying
the preset the document already holds recorded an undo step that changed
nothing. It now compares the new grid with the stored one, as
perspective.grid.set does through grid_kept, and records a step only when
the grid changes. When only whether the grid shows or its active plane
differs, the preset sets them without an undo step, as
perspective.grid.show and perspective.plane.set do.
This commit is contained in:
dotCooCoo
2026-11-14 02:35:48 -08:00
parent 2e9c9ab200
commit 7694627a39
5 changed files with 59 additions and 10 deletions
+2 -2
View File
@@ -26,7 +26,7 @@ pub use perspective::perspective_click;
use perspective::{
attach, edit_text, grid_set, grid_show, persp_draw, persp_move, persp_nudge, persp_transform, plane_match, plane_move, plane_set, release,
};
pub(crate) use perspective::{finish_edit_text, grid_of, perspective_rewrite, silent, store_grid, transform_again};
pub(crate) use perspective::{finish_edit_text, grid_kept, grid_of, perspective_rewrite, silent, store_grid, transform_again};
pub fn specs() -> Vec<CommandSpec> {
vec![
@@ -107,7 +107,7 @@ pub fn specs() -> Vec<CommandSpec> {
"Perspective Grid Preset",
[],
None,
"{kind: 1|2|3 (its normal view) | name: a preset (see perspective.presets.list)} reset the grid to the preset fitted to the first artboard (and show it; attached objects stay attached)",
"{kind: 1|2|3 (its normal view) | name: a preset (see perspective.presets.list)} reset the grid to the preset fitted to the first artboard (and show it; attached objects stay attached). No undo step when the document already holds that grid; when only whether it shows or its active plane differs, those are set without one",
has_doc,
super::perspgrid::grid_preset
),
@@ -18,7 +18,7 @@ pub(crate) fn store_grid(d: &mut Document, g: &PerspectiveGrid) {
/// Whether `d` holds `g` as [`store_grid`] writes it (with `attached` cleared): then storing `g`
/// leaves `d` as it is.
fn grid_kept(d: &Document, g: &PerspectiveGrid) -> bool {
pub(crate) fn grid_kept(d: &Document, g: &PerspectiveGrid) -> bool {
let mut g = g.clone();
g.attached.clear();
serde_json::to_value(&g).is_ok_and(|v| d.unknown.get(persp::DOC_KEY) == Some(&v))
+16 -5
View File
@@ -19,7 +19,7 @@ use vectorcraft_tools::distort::perspective::widget::{WidgetCorner, WidgetPlace}
use vectorcraft_tools::distort::perspective::{GridDefinition, PerspectiveGrid};
use vectorcraft_tools::{PointerEvent, PointerKind, ToolKey};
use super::distortcmds::{grid_of, silent, store_grid};
use super::distortcmds::{grid_kept, grid_of, silent, store_grid};
use super::{CommandSpec, EngineError, Result, Session, always, bad, bool_or, cmd, has_doc, str_param, switch_or, unique_name};
pub fn specs() -> Vec<CommandSpec> {
@@ -261,10 +261,21 @@ pub(crate) fn grid_preset(s: &mut Session, p: &Value) -> Result<Value> {
let g = s.perspective_preset_grid(&name, ab).ok_or_else(|| bad(C, format!("no perspective grid preset named `{name}`")))?;
// The objects stay attached and the view options stay as they were.
let g = PerspectiveGrid { attached: old.attached, locked: old.locked, lock_station: old.lock_station, snap: old.snap, rulers: old.rulers, ..g };
s.edit("Perspective Grid Preset", |d, _| {
store_grid(d, &g);
Ok(())
})?;
// When the document holds this grid already, apart from whether it shows and its active plane,
// the preset only sets those two, without an undo step (as perspective.grid.show and
// perspective.plane.set do).
let view = PerspectiveGrid { visible: old.visible, plane: old.plane, ..g.clone() };
if !grid_kept(&s.doc()?.doc, &view) {
s.edit("Perspective Grid Preset", |d, _| {
store_grid(d, &g);
Ok(())
})?;
} else if (old.visible, old.plane) != (g.visible, g.plane) {
silent(s, |cur| {
cur.visible = g.visible;
cur.plane = g.plane;
})?;
}
Ok(g.definition_json())
}
+36
View File
@@ -150,6 +150,42 @@ fn built_in_views_apply_by_name_or_type() {
assert!(s.execute("perspective.grid.preset", &json!({})).is_err());
}
#[test]
fn reapplying_the_current_grid_preset_records_no_undo_step() {
let mut s = session();
// On a document with no grid defined, a preset defines and shows one: an undo step.
run(&mut s, "perspective.grid.preset", json!({"kind": 2}));
assert_eq!(undo_len(&s), 1);
let g = grid(&s);
// The same preset again, by type or by name, leaves the grid as it is: no undo step.
run(&mut s, "perspective.grid.preset", json!({"kind": 2}));
run(&mut s, "perspective.grid.preset", json!({"name": "[2P-Normal View]"}));
assert_eq!((grid(&s), undo_len(&s)), (g.clone(), 1));
// Another active plane, or a hidden grid, is set back without an undo step.
run(&mut s, "perspective.plane.set", json!({"plane": "ground"}));
run(&mut s, "perspective.grid.show", json!({"visible": false}));
assert_ne!(grid(&s), g);
run(&mut s, "perspective.grid.preset", json!({"kind": 2}));
assert_eq!((grid(&s), undo_len(&s)), (g, 1));
// Another preset changes the grid.
run(&mut s, "perspective.grid.preset", json!({"kind": 3}));
assert_eq!((grid(&s).kind, undo_len(&s)), (3, 2));
}
#[test]
fn define_grid_with_unchanged_fields_records_no_undo_step() {
let mut s = session();
// On a document with no grid defined, `perspective.grid.set` stores the grid: an undo step,
// and the grid no longer follows the first artboard.
run(&mut s, "perspective.grid.set", json!({}));
assert_eq!((run(&mut s, "perspective.grid.get", json!({}))["defined"].clone(), undo_len(&s)), (json!(true), 1));
// The stored grid's own values leave it as it is: no undo step.
let g = grid(&s);
run(&mut s, "perspective.grid.set", json!({}));
run(&mut s, "perspective.grid.set", json!({"cell": g.cell, "horizon": g.horizon}));
assert_eq!((grid(&s), undo_len(&s)), (g, 1));
}
#[test]
fn presets_save_rename_delete_and_protect_the_built_in_ones() {
let mut s = session();
+4 -2
View File
@@ -10028,8 +10028,10 @@ Perspective grid presets: `perspective.presets.list` lists the built-in views (`
`[1P-High View]`, `[2P-Normal View]`, `[2P-Low View]`, `[2P-High View]`, `[3P-Normal View]`, `[3P-Low View]`;
`builtIn: true`, fitted to the first artboard, protected), then the saved ones, each with its Define Grid fields.
`perspective.grid.preset {name}` (or `{kind}`, that type's normal view) resets the grid to one, fitted to the first
artboard. `perspective.presets.save {name?, newName?, preset?, …fields}` saves a preset: just `{name}` saves the
document's grid (View → Perspective Grid → Save Grid as Preset). `perspective.presets.delete {name}`,
artboard. It adds no undo step when the document already holds that grid. When only whether the grid shows or its
active plane differs, it sets them without an undo step, as `perspective.grid.show` and `perspective.plane.set` do.
`perspective.presets.save {name?, newName?, preset?, …fields}` saves a preset: just `{name}` saves the document's grid
(View → Perspective Grid → Save Grid as Preset). `perspective.presets.delete {name}`,
`perspective.presets.export {names?, path?}` (a `.vcperspective` JSON file; without `path` it returns `data`) and
`perspective.presets.import {path? | data? | dataBase64?, replace?}` manage them. Saved presets are kept with the
preferences, and a grid whose fields are a preset's is named after it (`name`).