mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-14 20:36:02 +00:00
Merge pull request #7477 from dotCooCoo/fix/dimension-second-point-guides
Dimension tool: no construction guides through a first point off the anchors
This commit is contained in:
@@ -1771,8 +1771,9 @@ fn both_points_snap_onto_anchors_with_a_visible_indicator() {
|
||||
}
|
||||
|
||||
/// Smart Guides for the Linear picks: hovering away from any line, the first point slides into
|
||||
/// line with another object's corner and its guides show before the click; the second slides onto
|
||||
/// the construction guide through the first. The dimension measures between the snapped points.
|
||||
/// line with another object's corner and its guides show before the click; the second slides into
|
||||
/// line with the other corner, with no construction guide through the first point pulling it
|
||||
/// sideways. The dimension measures between the snapped points.
|
||||
#[test]
|
||||
fn linear_picks_show_and_snap_to_smart_guides() {
|
||||
use vectorcraft_engine::tools::Overlay;
|
||||
@@ -1788,18 +1789,20 @@ fn linear_picks_show_and_snap_to_smart_guides() {
|
||||
assert!(o.iter().any(|o| matches!(o, Overlay::Line { .. } | Overlay::Label { .. })), "its guides show before the click: {o:?}");
|
||||
ev(&mut s, PointerKind::Down, 250.0, 202.5);
|
||||
ev(&mut s, PointerKind::Up, 250.0, 202.5);
|
||||
// Straight below the first point, 1.5 pt off: the rubber band ends on the vertical through it.
|
||||
ev(&mut s, PointerKind::Move, 251.5, 260.0);
|
||||
// Below the first point, 1.5 pt to its right and 2.5 pt off the bottom edge's level: the
|
||||
// rubber band ends on that level, where the pointer is across it.
|
||||
ev(&mut s, PointerKind::Move, 251.5, 262.5);
|
||||
let end = overlays(&mut s).into_iter().find_map(|o| if let Overlay::Line { b, dashed: true, .. } = o { Some(b) } else { None }).unwrap();
|
||||
assert_eq!((end.x, end.y), (250.0, 260.0), "on the construction guide");
|
||||
ev(&mut s, PointerKind::Down, 251.5, 260.0);
|
||||
ev(&mut s, PointerKind::Up, 251.5, 260.0);
|
||||
assert_eq!((end.x, end.y), (251.5, 260.0), "in line with the bottom edge, not on a vertical through the first point");
|
||||
ev(&mut s, PointerKind::Down, 251.5, 262.5);
|
||||
ev(&mut s, PointerKind::Up, 251.5, 262.5);
|
||||
ev(&mut s, PointerKind::Move, 220.0, 230.0);
|
||||
assert!(!overlays(&mut s).iter().any(|o| matches!(o, Overlay::Label { .. })), "placing: the guides are gone");
|
||||
ev(&mut s, PointerKind::Down, 220.0, 230.0);
|
||||
ev(&mut s, PointerKind::Up, 220.0, 230.0);
|
||||
let id = s.doc().unwrap().selection.objects[0].0;
|
||||
assert_eq!(text(&s, id), "60.00 pt");
|
||||
// Aligned from (250, 200) to (251.5, 260): √(1.5² + 60²).
|
||||
assert_eq!(text(&s, id), "60.02 pt");
|
||||
}
|
||||
|
||||
/// The page content streams of exported PDF `v`, inflated.
|
||||
|
||||
+102
-14
@@ -240,12 +240,18 @@ fn points_source(cx: &ToolContext, (a, ra): (Point, &Option<PointRef>), (b, rb):
|
||||
impl DimensionTool {
|
||||
/// Where a Linear pick at `p` lands: on the nearest anchor of any path, within reach (kept as
|
||||
/// a link to its path); else where Smart Guides put it (another object's anchor or centre, in
|
||||
/// line with one, or for the second point on a construction guide through the first), its
|
||||
/// guides showing; else on the closest point of the line under the pointer; else `p` itself.
|
||||
/// line with one, or for the second point square to a segment or grazing a curve from the
|
||||
/// first with Snap to Perpendicular or Snap to Tangent on, or on a construction guide through
|
||||
/// a first point picked on an anchor), its guides showing; else on the closest point of the
|
||||
/// line under the pointer; else `p` itself. A first point not on an anchor has no
|
||||
/// construction guides through it.
|
||||
/// A point the guides leave where the pointer is (right on a guide line) stays there, and with
|
||||
/// Snap to Pixel or Snap to Grid on a point stays where they place it.
|
||||
fn snap_linear(&mut self, cx: &ToolContext, p: Point) -> (Point, Option<PointRef>) {
|
||||
let leave = self.points.last().map(|(first, _)| crate::guides::Leave::segment(cx, *first, false));
|
||||
let leave = self.points.last().map(|(first, anchor)| match anchor {
|
||||
Some(_) => crate::guides::Leave::segment(cx, *first, false),
|
||||
None => crate::guides::Leave::contact(*first),
|
||||
});
|
||||
let guided = self.snap.hover(cx, p, &[], leave.as_ref());
|
||||
if let Some((point, reference)) = anchor_under(cx, p) {
|
||||
self.snap.clear();
|
||||
@@ -902,10 +908,11 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// A point that Smart Guides leave where the pointer already is (the pointer right on a
|
||||
/// construction guide or in line with a target) stays on the guide the overlays show. It
|
||||
/// does not move to the nearest point of a segment within the pick's reach. Off the guides,
|
||||
/// a point near a segment still lands on it.
|
||||
/// A point that Smart Guides leave where the pointer already is (the pointer right in line
|
||||
/// with a target) stays on the guide the overlays show. It does not move to the nearest point
|
||||
/// of a segment within the pick's reach. Off the guides, a point near a segment still lands
|
||||
/// on it, also straight below the first point: the second point has no construction guide
|
||||
/// through a first point that is not on an anchor.
|
||||
#[test]
|
||||
fn a_point_on_a_guide_stays_on_the_guide() {
|
||||
let (d, _) = doc_with_rect();
|
||||
@@ -923,17 +930,22 @@ mod tests {
|
||||
// Off the guides, beside the edge: the closest point of the edge.
|
||||
let (on_edge, reference) = t.snap_linear(&c, Point::new(206.0, 170.5));
|
||||
assert!(on_edge.distance(Point::new(200.0, 170.5)) < 1e-4 && reference.is_none(), "{on_edge:?}");
|
||||
// The first point, above the rectangle, then straight down along the vertical
|
||||
// construction guide through it, past the right edge.
|
||||
// The first point, above the rectangle, then straight down past the right edge: no
|
||||
// vertical guide through the first point holds the second, so beside the edge it lands
|
||||
// on the edge, and in line with the center it stays in line.
|
||||
click(&mut t, 206.0, 50.0);
|
||||
assert_eq!(t.points, vec![(Point::new(206.0, 50.0), None)]);
|
||||
for y in [150.0, 170.5, 190.0] {
|
||||
assert_eq!(t.snap_linear(&c, Point::new(206.0, y)), (Point::new(206.0, y), None), "on the construction guide at y = {y}");
|
||||
assert!(!t.snap.guides().is_empty());
|
||||
for y in [170.5, 190.0] {
|
||||
let (q, reference) = t.snap_linear(&c, Point::new(206.0, y));
|
||||
assert!(q.distance(Point::new(200.0, y)) < 1e-4 && reference.is_none(), "beside the edge at y = {y}: {q:?}");
|
||||
}
|
||||
click(&mut t, 206.0, 170.5);
|
||||
assert_eq!(t.snap_linear(&c, Point::new(207.0, 300.0)), (Point::new(207.0, 300.0), None), "away from the art");
|
||||
assert!(t.snap.guides().is_empty());
|
||||
assert_eq!(t.snap_linear(&c, Point::new(206.0, 150.0)), (Point::new(206.0, 150.0), None));
|
||||
assert!(!t.snap.guides().is_empty());
|
||||
click(&mut t, 206.0, 150.0);
|
||||
let dim = t.pending.as_ref().expect("both points were picked");
|
||||
assert_eq!((dim.a, dim.b), (Point::new(206.0, 50.0), Point::new(206.0, 170.5)));
|
||||
assert_eq!((dim.a, dim.b), (Point::new(206.0, 50.0), Point::new(206.0, 150.0)));
|
||||
// The second point, off the edge, is held against the rectangle's anchors.
|
||||
let Some(DimensionSource::Points { free_b: Some(free), .. }) = &dim.source else { panic!("{:?}", dim.source) };
|
||||
assert!(matches!(free, FreePoint::Anchors { .. }), "{free:?}");
|
||||
@@ -950,6 +962,82 @@ mod tests {
|
||||
assert_eq!(DimensionTool::default().snap_linear(&pixel, Point::new(206.0, 170.0)), (Point::new(206.0, 170.0), None));
|
||||
}
|
||||
|
||||
/// After a first point that is not on an anchor, the second point still lands where the line
|
||||
/// from the first point meets a slanted segment square or grazes a curve, with the
|
||||
/// "perpendicular" or "tangent" label, at places no construction guide through the first
|
||||
/// point reaches. With Snap to Perpendicular and Snap to Tangent off it stays at the pointer.
|
||||
#[test]
|
||||
fn the_second_point_lands_square_to_a_segment_or_grazing_a_curve() {
|
||||
use vectorcraft_doc::Appearance;
|
||||
use vectorcraft_geom::{Rect, shapes};
|
||||
let (mut d, _) = doc_with_rect();
|
||||
let l = d.layers[0].id;
|
||||
let (line, circle) = (d.alloc_id(), d.alloc_id());
|
||||
let slanted = shapes::line(Point::new(300.0, 100.0), Point::new(400.0, 150.0));
|
||||
d.insert(Some(l), 0, Node::path(line, slanted, Appearance::default_art())).unwrap();
|
||||
let round = shapes::ellipse(Rect::new(420.0, 370.0, 480.0, 430.0));
|
||||
d.insert(Some(l), 0, Node::path(circle, round, Appearance::default_art())).unwrap();
|
||||
let (s, p) = (Selection::default(), paint());
|
||||
let c = cx(&d, &s, &p);
|
||||
let first = Point::new(300.0, 250.0);
|
||||
let picked = |c: &ToolContext| {
|
||||
let mut t = DimensionTool::default();
|
||||
for kind in [PointerKind::Move, PointerKind::Down, PointerKind::Up] {
|
||||
t.pointer(c, &PointerEvent::new(kind, first.x, first.y));
|
||||
}
|
||||
assert_eq!(t.points, vec![(first, None)]);
|
||||
t
|
||||
};
|
||||
let labeled = |t: &DimensionTool, label: &str| t.snap.guides().iter().any(|o| matches!(o, Overlay::Label { text, .. } if text == label));
|
||||
// The foot of the perpendicular from (300, 250) to the line is (360, 130), along no
|
||||
// construction guide angle from the first point.
|
||||
let mut t = picked(&c);
|
||||
let (q, reference) = t.snap_linear(&c, Point::new(361.0, 132.0));
|
||||
assert!(q.distance(Point::new(360.0, 130.0)) < 1e-4 && reference.is_none(), "{q:?}");
|
||||
assert!(labeled(&t, "perpendicular"), "{:?}", t.snap.guides());
|
||||
// Where a line from the first point grazes the circle about (450, 400), radius 30.
|
||||
let (center, radius) = (Point::new(450.0, 400.0), 30.0);
|
||||
let away = first - center;
|
||||
let angle = away.y.atan2(away.x) + (radius / away.hypot()).acos();
|
||||
let touch = center + Vec2::new(angle.cos(), angle.sin()) * radius;
|
||||
let (q, _) = t.snap_linear(&c, touch + Vec2::new(0.5, 0.5));
|
||||
assert!((q.distance(center) - radius).abs() < 0.05, "{q:?} is on the circle");
|
||||
assert!(((q - first).normalize().dot((q - center).normalize())).abs() < 0.01, "{q:?} grazes the circle");
|
||||
assert!(labeled(&t, "tangent"), "{:?}", t.snap.guides());
|
||||
// With both options off, the pointer near the foot lands on the closest point of the line
|
||||
// instead.
|
||||
let off = ToolContext { snap_perpendicular: false, snap_tangent: false, ..cx(&d, &s, &p) };
|
||||
let mut t = picked(&off);
|
||||
let (q, _) = t.snap_linear(&off, Point::new(361.0, 132.0));
|
||||
assert!(q.distance(Point::new(360.0, 130.0)) > 0.5 && !labeled(&t, "perpendicular"), "{q:?}");
|
||||
}
|
||||
|
||||
/// After a first point picked on an anchor, the second point still lands on the construction
|
||||
/// guides through it.
|
||||
#[test]
|
||||
fn the_second_point_after_an_anchor_lands_on_construction_guides() {
|
||||
let (d, id) = doc_with_rect();
|
||||
let (s, p) = (Selection::default(), paint());
|
||||
let c = cx(&d, &s, &p);
|
||||
let mut t = DimensionTool::default();
|
||||
for kind in [PointerKind::Move, PointerKind::Down, PointerKind::Up] {
|
||||
t.pointer(&c, &PointerEvent::new(kind, 200.0, 200.0));
|
||||
}
|
||||
assert_eq!(t.points, vec![(Point::new(200.0, 200.0), Some(PointRef { node: id, subpath: 0, anchor: 2 }))]);
|
||||
// 1.5 units right and below the 45° guide up and to the right of the anchor.
|
||||
let (q, reference) = t.snap_linear(&c, Point::new(331.0, 72.0));
|
||||
assert!(q.distance(Point::new(329.5, 70.5)) < 1e-4 && reference.is_none(), "{q:?}");
|
||||
assert!(!t.snap.guides().is_empty());
|
||||
// The same offset from a first point off the anchors stays at the pointer.
|
||||
let mut t = DimensionTool::default();
|
||||
for kind in [PointerKind::Move, PointerKind::Down, PointerKind::Up] {
|
||||
t.pointer(&c, &PointerEvent::new(kind, 240.0, 240.0));
|
||||
}
|
||||
assert_eq!(t.points, vec![(Point::new(240.0, 240.0), None)]);
|
||||
assert_eq!(t.snap_linear(&c, Point::new(371.0, 112.0)), (Point::new(371.0, 112.0), None));
|
||||
assert!(t.snap.guides().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn placement_press_previews_without_a_prior_move() {
|
||||
let (d, _) = doc_with_rect();
|
||||
|
||||
@@ -257,10 +257,11 @@ impl Leave {
|
||||
self
|
||||
}
|
||||
|
||||
/// The straight segment from the anchor `at` to a point being dragged (an open path's end
|
||||
/// moved with Direct Selection): the point may land square to a segment or grazing a curve,
|
||||
/// but is not held to Shift's angles or the construction guides, which a moved anchor
|
||||
/// does not follow.
|
||||
/// The straight segment from the point `at` to a point being placed (an open path's end
|
||||
/// moved with Direct Selection, or the Dimension tool's second Linear point after a first
|
||||
/// point that is not on an anchor): the point may
|
||||
/// land square to a segment or grazing a curve, but is not held to Shift's angles or the
|
||||
/// construction guides through `at`.
|
||||
pub fn contact(at: Point) -> Self {
|
||||
Self { at, shift: None, angles: &[], contact: true }
|
||||
}
|
||||
|
||||
+9
-5
@@ -15,13 +15,17 @@ The **Dimension Tool** is in the Modify section of the toolbar. Its strip offers
|
||||
path's length. A press on an anchor picks it when the pointer moves less than 8 pixels
|
||||
before the release; a longer drag drags out the dimension of the segment under the pointer. A
|
||||
curved segment is measured from end to end. With Smart Guides on, both points also snap into line with other objects'
|
||||
anchors and centres, and the second onto the construction guides through the first; the guides
|
||||
anchors and centres. With Snap to Perpendicular or Snap to Tangent on, the second point also
|
||||
snaps onto a segment where the line from the first meets it square or grazes a curve. When the
|
||||
first point was picked on an anchor, the second also snaps onto the construction guides through
|
||||
it; any other first point has no construction guides through it. The guides
|
||||
show while hovering, before each click. VectorCraft's own rules decide where a point lands: an
|
||||
anchor within 8 pixels takes it first; a point on a guide stays on the guide where the pointer
|
||||
is, except that it moves onto a segment within the Smart Guides snapping tolerance, or onto the
|
||||
place where a construction guide crosses another object's alignment line within twice that
|
||||
tolerance; a point that no anchor, guide, grid or pixel snapping places lands on the closest
|
||||
point of a segment within 8 pixels.
|
||||
is, except that it moves onto a segment within the Smart Guides snapping tolerance, or, on a
|
||||
construction guide or a Snap to Glyph guide that runs at an angle, onto the place where that
|
||||
guide crosses another object's alignment line within twice that tolerance; a point that no
|
||||
anchor, guide, grid or pixel snapping places lands on the closest point of a segment within 8
|
||||
pixels.
|
||||
A linear dimension is aligned, horizontal or vertical, as the
|
||||
[linear dimensions help](https://helpx.adobe.com/illustrator/desktop/measure-and-align/plot-and-measure/plot-linear-dimensions-of-objects.html)
|
||||
describes: after the second click, clicking again places an aligned dimension, and moving the
|
||||
|
||||
@@ -594,7 +594,8 @@ since 2026-10-01.
|
||||
**Drawing tools, Star inner radius held with Ctrl/Cmd:** While a star is dragged out, Ctrl/Cmd keeps the inner radius the star showed when the key went down, also when the key is pressed with the pointer still. `a_star_drag_holds_its_inner_radius_with_ctrl_and_straightens_with_alt` and `a_star_drag_holds_the_inner_radius_it_shows_when_ctrl_goes_down` cover it, with Alt's straight shoulders and a drag canceled with Escape.
|
||||
**Drawing tools, Dimension tool horizontal and vertical linear dimensions:** A linear dimension between two clicked points or dragged out from a segment is aligned, horizontal or vertical, chosen by where the pointer moves while it is placed, and a click again by the second point gives aligned; `dimension.create` and `dimension.update` take `orientation`, and the dimension stays linked to the anchors it measures, keeps its orientation when it or the art turns, with a line that lay outside the points kept outside them; an unlinked horizontal or vertical dimension that is reflected has its line on the reflected side. `the_pointer_chooses_aligned_horizontal_or_vertical`, `two_points_measure_horizontally_vertically_or_aligned`, `clicking_again_by_the_second_point_places_an_aligned_dimension`, `a_segment_drag_measures_horizontally_vertically_or_aligned`, `points_on_a_vertical_line_measure_aligned`, `horizontal_and_vertical_dimensions_draw_lines_along_their_axis`, `turned_horizontal_dimensions_stay_horizontal`, `a_linked_horizontal_dimension_keeps_its_gap_from_turned_art`, `a_linked_horizontal_dimension_below_or_between_its_points_follows_turned_art`, `a_reflected_horizontal_dimension_keeps_its_line_on_the_reflected_side`, `a_dimension_without_an_orientation_reads_as_aligned`, `dimension_orientation_is_checked` and `a_turned_dimension_keeps_its_line_on_its_side` cover it.
|
||||
**Drawing tools, Dimension tool points that are not anchors follow the art:** A clicked point on a segment stays at its place along the segment, also when the segment curves, and a point off the art is linked to anchors (or, when the anchors lie on one line, anchors and handles) of the path, so both ends of the dimension follow when the art is moved, scaled, turned or reflected, also when the point was placed by a Smart Guide and when the dimension is saved and opened again; a linked linear dimension of reflected art (other than art on one line) has its line on the reflected side; deleting the art, changing its anchor count or whether it is closed, turning an angle's arm into a curve, or making a measured circle or arc no longer circular leaves the dimension where it was, no longer linked, instead of removing it. `a_dimension_to_a_point_on_a_segment_follows_the_art_at_both_ends`, `a_dimension_to_a_point_off_the_art_moves_with_the_art`, `a_dimension_whose_art_changes_its_anchors_stays_where_it_was`, `a_dimension_whose_art_is_opened_stays_where_it_was`, `angle_and_arc_dimensions_whose_art_no_longer_fits_stay_where_they_were`, `a_dimension_that_cannot_be_drawn_again_keeps_its_art`, `a_linked_dimension_of_reflected_art_has_its_line_on_the_reflected_side`, `a_dimension_to_a_point_off_a_line_follows_the_line`, `a_point_off_an_arch_is_reflected_with_the_arch`, `a_point_beside_an_almost_straight_curve_follows_it`, `a_point_off_a_loop_with_its_anchors_in_one_place_follows_it`, `a_point_beside_a_reflected_line_moves_as_a_half_turn_would`, `a_point_on_a_segment_stays_on_it_when_the_segment_curves`, `a_linked_dimension_of_a_reflected_curve_has_its_line_on_the_reflected_side`, `a_dimension_of_art_reflected_across_its_points_changes_sides`, `a_dimension_between_paths_reflected_each_in_its_own_way_keeps_its_offset`, `free_points_survive_a_native_file`, `dimension_free_points_are_checked`, `points_off_anchors_are_held_against_the_path` and `a_point_smart_guides_place_is_held_against_the_path` cover it.
|
||||
**Drawing tools, Dimension tool points on a Smart Guide stay on the guide:** By VectorCraft's own rule, a clicked point that lies on a Smart Guide already, such as a point straight below the first one on the vertical construction guide through it, or one in line with an object's center, stays where the pointer is when no anchor is within 8 pixels and no segment is within the Smart Guides snapping tolerance, instead of moving onto a segment farther than that tolerance and within 8 pixels; with Snap to Grid or Snap to Pixel on, a point stays on the grid or the whole pixel those options give. `a_point_on_a_guide_stays_on_the_guide` covers it.
|
||||
**Drawing tools, Dimension tool points on a Smart Guide stay on the guide:** By VectorCraft's own rule, a clicked point that lies on a Smart Guide already, such as one in line with an object's center, stays where the pointer is when no anchor is within 8 pixels and no segment is within the Smart Guides snapping tolerance, instead of moving onto a segment farther than that tolerance and within 8 pixels; with Snap to Grid or Snap to Pixel on, a point stays on the grid or the whole pixel those options give. `a_point_on_a_guide_stays_on_the_guide` covers it.
|
||||
**Drawing tools, Dimension tool second point without construction guides through a first point off the anchors:** By VectorCraft's own rule, with Smart Guides on, the second Linear point does not snap to the construction guides through a first point that is not on an anchor, so a click beside a segment straight below such a first point lands on the segment; after a first point picked on an anchor, the construction guides through it still apply. The second point still snaps to anchors, centers and alignment lines, and with Snap to Perpendicular or Snap to Tangent on, it lands where the line from the first point meets a segment square or grazes a curve. `a_point_on_a_guide_stays_on_the_guide`, `the_second_point_after_an_anchor_lands_on_construction_guides`, `the_second_point_lands_square_to_a_segment_or_grazing_a_curve` and `linear_picks_show_and_snap_to_smart_guides` cover it.
|
||||
**Drawing tools, Dimension tool dimensions selected with their art keep their lines and label:** Object › Expand, Object › Expand Appearance and Object › Path › Outline Stroke leave a dimension selected with art it measures, directly or in selected groups without fills, strokes or effects of their own, as it is and change only the other art; when the new art no longer fits its link, the dimension stays where it was, no longer linked, and undo links it again. `expand_leaves_a_dimension_selected_with_its_art_as_it_is`, `expand_appearance_leaves_a_dimension_selected_with_its_art_as_it_is`, `outline_stroke_leaves_a_dimension_selected_with_its_art_as_it_is`, `a_dimension_selected_with_art_that_has_nothing_to_expand_changes_nothing`, `a_group_effect_expanded_reaches_the_dimension_it_holds`, `expand_keeps_a_dimension_in_its_asset_and_its_variable_binding`, `a_dimension_keeps_no_link_to_the_art_expand_replaced`, `a_dimension_outlasts_the_art_outline_stroke_or_expand_appearance_replaced`, `a_dimension_outlasts_the_stroke_expand_outlined` and `expand_appearance_in_safe_mode_leaves_a_dimension_selected_with_its_art` cover it.
|
||||
**Live trace result bounds and selection highlights exclude the retained bitmap except in source-image views:** `live_trace_result_bounds_exclude_the_retained_bitmap_at_any_angle` checks every TraceView, geometric and visual bounds and rotated result bounds. `live_trace_selection_highlight_excludes_the_retained_source_rectangle` checks result highlights and the existing ordinary-group policy. Shared editing-child traversal also preserves Shaper handling.
|
||||
**Artboard Options shares existing presets and orientation controls and can fit artwork without moving it; artwork fitting includes all drawn document art across artboards and off-board; artwork bounds are cached by active document and revision, and selection uses the shared bounds cache across artboards:** Shared existing preset definitions are reused; no external preset assets are added. Presets preserve each selected artboard's orientation, and orientation choices apply even when the active board already matches. Fit operations draft bounds until OK, affect only the active artboard and leave artwork in place. Regression coverage checks draft/Cancel, selected-set application, active-only fitting and Undo.
|
||||
|
||||
Reference in New Issue
Block a user