mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-13 22:21:59 +00:00
Pencil: Fidelity keeps its screen size at low zoom
The Pencil and the Paintbrush send their Fidelity in points at the current zoom, but path.freehand read at most 100 pt. At 3.13% zoom a 20 px Fidelity (639 pt) was fitted at 100 pt, the same as 3.13 px on screen, so a stroke drawn zoomed out kept far more anchors than the same stroke at 100%. - path.freehand, and brush.freehand, which runs it, take a Fidelity from MIN_FIT_TOLERANCE to MAX_FIT_TOLERANCE (100 / 0.0313 pt, the 100 px field maximum at the lowest zoom). A value outside that range is now rejected with an error instead of being read as the nearer end. The default stays 1.5 pt and path.blob keeps its 100 pt bound. - The Pencil and the Paintbrush keep the value they send within that range, so a stroke draws at any zoom. - An extending stroke still closes the path within four Fidelities of its other end, at most 400 pt, as before. It conflicts with open #7089 in crates/tools/src/draw2.rs. Docs: docs/mcp.md, docs/target-app-parity.md, path.freehand params.
This commit is contained in:
@@ -11,7 +11,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Pencil",
|
||||
[],
|
||||
None,
|
||||
"{points: [[x,y]…] or [[x,y,pressure]…] or [[x,y,pressure,tiltX,tiltY,rotation]…] (pen pressure 0..1: the stroke keeps it, for Calligraphic and Scatter brushes with Pressure variation; a point without one presses fully. Pen tilt in degrees, each −90..90, x positive leaning right and y leaning down the page, and barrel rotation in degrees clockwise: the stroke keeps how far the pen leaned, which way and how far its barrel turned, for brushes with Tilt, Bearing and Rotation variation; null for what a point lacks), fidelity?: pt (1.5), fit?: bool (true; false: the points are joined by straight segments, as the Pencil previews a stroke with Live curve fitting off), closed?, style?: \"pencil\"|\"brush\", fill?: bool, keepSelected?: bool (true: the drawn or continued path is selected; false: a new path leaves nothing selected, and a continued or redrawn one is deselected while the rest of the selection stays), extend?: {id, end: \"start\"|\"end\"}, connect?: {id, end: \"start\"|\"end\"}, redraw?: {id, within?: pt (12)}} fit a freehand stroke → {id}. extend continues the path from that end; with connect, the stroke ends on that end of another path's last open subpath and joins it on, in one path with the extended path's appearance (the other path is removed when nothing of it is left); redraw (Edit Selected Paths) replaces the section of the path between where the stroke starts and ends, each within `within` of one subpath (the shorter way round a closed one), and draws a new path when the stroke doesn't end on it",
|
||||
"{points: [[x,y]…] or [[x,y,pressure]…] or [[x,y,pressure,tiltX,tiltY,rotation]…] (pen pressure 0..1: the stroke keeps it, for Calligraphic and Scatter brushes with Pressure variation; a point without one presses fully. Pen tilt in degrees, each −90..90, x positive leaning right and y leaning down the page, and barrel rotation in degrees clockwise: the stroke keeps how far the pen leaned, which way and how far its barrel turned, for brushes with Tilt, Bearing and Rotation variation; null for what a point lacks), fidelity?: pt (1.5; from 0.000015625 (0.01 / 640) to 100 / 0.0313 (about 3194.89), the Pencil's and the Paintbrush's 0.01 to 100 px Fidelity at the highest and the lowest zoom; a value outside that range is an error. The tools send their Fidelity in pixels divided by the zoom, kept within the range), fit?: bool (true; false: the points are joined by straight segments, as the Pencil previews a stroke with Live curve fitting off), closed?, style?: \"pencil\"|\"brush\", fill?: bool, keepSelected?: bool (true: the drawn or continued path is selected; false: a new path leaves nothing selected, and a continued or redrawn one is deselected while the rest of the selection stays), extend?: {id, end: \"start\"|\"end\"}, connect?: {id, end: \"start\"|\"end\"}, redraw?: {id, within?: pt (12)}} fit a freehand stroke → {id}. extend continues the path from that end; with connect, the stroke ends on that end of another path's last open subpath and joins it on, in one path with the extended path's appearance (the other path is removed when nothing of it is left); redraw (Edit Selected Paths) replaces the section of the path between where the stroke starts and ends, each within `within` of one subpath (the shorter way round a closed one), and draws a new path when the stroke doesn't end on it",
|
||||
has_doc,
|
||||
freehand
|
||||
),
|
||||
|
||||
@@ -151,7 +151,11 @@ pub(super) fn freehand(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
}
|
||||
let pen = PenRecord::of_samples(&samples);
|
||||
let mut pts: Vec<Point> = samples.into_iter().map(|s| s.pt).collect();
|
||||
let tol = number_or(p, "fidelity", 1.5, C)?.clamp(vectorcraft_tools::draw2::MIN_FIT_TOLERANCE, 100.0);
|
||||
let tol = number_or(p, "fidelity", 1.5, C)?;
|
||||
let (lo, hi) = (vectorcraft_tools::draw2::MIN_FIT_TOLERANCE, vectorcraft_tools::draw2::MAX_FIT_TOLERANCE);
|
||||
if !(lo..=hi).contains(&tol) {
|
||||
return Err(bad(C, format!("`fidelity` must be from {lo} to {hi} pt")));
|
||||
}
|
||||
// The tolerance the stroke is fitted at; with `fit: false` it is not fitted.
|
||||
let fit_tol = switch_or(p, "fit", true, C)?.then_some(tol);
|
||||
let closed = switch_or(p, "closed", false, C)?;
|
||||
@@ -200,7 +204,9 @@ pub(super) fn freehand(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
let close_tol = (tol * 4.0).max(6.0);
|
||||
// An extending stroke that ends within four Fidelities of the path's other end closes it:
|
||||
// at least 6 pt and at most 400 pt, whatever the Fidelity it is fitted at.
|
||||
let close_tol = (tol * 4.0).clamp(6.0, 400.0);
|
||||
let closing = connect.is_none() && sp.anchors.len() >= 2 && pts.last().is_some_and(|p| p.distance(first) <= close_tol);
|
||||
if closing && let Some(l) = pts.last_mut() {
|
||||
*l = first;
|
||||
|
||||
@@ -4458,6 +4458,12 @@ fn blob_anchors(s: &mut Session, fidelity: f64) -> usize {
|
||||
/// [`blob_anchors`] with the blob scaled by `scale`: drawn the same on screen zoomed in 1 / `scale`
|
||||
/// times.
|
||||
fn scaled_blob_anchors(s: &mut Session, fidelity: f64, scale: f64) -> usize {
|
||||
blob_anchors_through(s, "path.freehand", &json!({}), Some(fidelity), scale)
|
||||
}
|
||||
|
||||
/// [`scaled_blob_anchors`] drawn with `command` and the params in `extra`, at `fidelity` (with none,
|
||||
/// the command's default).
|
||||
fn blob_anchors_through(s: &mut Session, command: &str, extra: &serde_json::Value, fidelity: Option<f64>, scale: f64) -> usize {
|
||||
let pts: Vec<_> = (0..1200)
|
||||
.map(|i| {
|
||||
let a = i as f64 / 1200.0 * std::f64::consts::TAU;
|
||||
@@ -4465,7 +4471,14 @@ fn scaled_blob_anchors(s: &mut Session, fidelity: f64, scale: f64) -> usize {
|
||||
json!([(400.0 + r * a.cos()) * scale, (300.0 + r * a.sin()) * scale])
|
||||
})
|
||||
.collect();
|
||||
let r = s.execute("path.freehand", &json!({"points": pts, "closed": true, "fidelity": fidelity, "keepSelected": false})).unwrap();
|
||||
let mut p = json!({"points": pts, "closed": true, "keepSelected": false});
|
||||
if let Some(f) = fidelity {
|
||||
p["fidelity"] = json!(f);
|
||||
}
|
||||
if let (Some(p), Some(extra)) = (p.as_object_mut(), extra.as_object()) {
|
||||
p.extend(extra.clone());
|
||||
}
|
||||
let r = s.execute(command, &p).unwrap();
|
||||
path(s, NodeId(r["id"].as_u64().unwrap())).subpaths.iter().map(|sp| sp.anchors.len()).sum()
|
||||
}
|
||||
|
||||
@@ -4477,13 +4490,11 @@ fn pencil_fidelity_goes_down_to_a_hundredth_and_lower_keeps_more() {
|
||||
assert!(counts.windows(2).all(|w| w[0] <= w[1]), "{counts:?}");
|
||||
// Below the old floor of 0.05 the stroke keeps more of itself.
|
||||
assert!(counts[4] > counts[2], "{counts:?}");
|
||||
// Below the floor, the lowest Fidelity (0.01 px) at the highest zoom (64000 %), is read as the
|
||||
// floor (#5729).
|
||||
// The floor is the lowest Fidelity (0.01 px) at the highest zoom (64000 %) (#5729); below it,
|
||||
// `pencil_takes_fidelity_within_its_range_and_rejects_the_rest`.
|
||||
let floor = vectorcraft_tools::draw2::MIN_FIDELITY / 640.0;
|
||||
let at_floor = blob_anchors(&mut s, floor);
|
||||
assert!(at_floor >= counts[4], "{counts:?}");
|
||||
assert_eq!(blob_anchors(&mut s, floor / 10.0), at_floor);
|
||||
assert_eq!(blob_anchors(&mut s, 0.0), at_floor);
|
||||
}
|
||||
|
||||
/// #5729: the tools send Fidelity / zoom, so a stroke drawn the same on screen zoomed in keeps as
|
||||
@@ -4499,6 +4510,89 @@ fn pencil_fit_keeps_its_anchors_on_a_stroke_drawn_zoomed_in() {
|
||||
}
|
||||
}
|
||||
|
||||
/// The tools send Fidelity / zoom up to the highest Fidelity (100 px) at the lowest zoom (3.13 %):
|
||||
/// a stroke drawn the same on screen zoomed out keeps as many anchors as at 100 %, as a Pencil or a
|
||||
/// Paintbrush stroke and through `brush.freehand` with a brush. A tolerance above
|
||||
/// `MAX_FIT_TOLERANCE` is an error, and a stroke given no Fidelity is fitted at 1.5 pt.
|
||||
#[test]
|
||||
fn pencil_fit_keeps_its_anchors_on_a_stroke_drawn_zoomed_out() {
|
||||
let mut s = session();
|
||||
let max = vectorcraft_tools::draw2::MAX_FIT_TOLERANCE;
|
||||
// The blob six times the size, so that 100 px still keeps some of its waves.
|
||||
let big = 6.0;
|
||||
for (command, extra) in [
|
||||
("path.freehand", json!({})),
|
||||
("path.freehand", json!({"style": "brush"})),
|
||||
("brush.freehand", json!({"style": "brush", "brush": "3 pt. Round"})),
|
||||
] {
|
||||
for fidelity in [20.0, 100.0] {
|
||||
let want = blob_anchors_through(&mut s, command, &extra, Some(fidelity), big);
|
||||
for zoom in [0.2, 0.0313] {
|
||||
let got = blob_anchors_through(&mut s, command, &extra, Some(fidelity / zoom), big / zoom);
|
||||
assert_eq!(got, want, "{command} {extra}: {fidelity} px at {zoom}x");
|
||||
// Fitted at 100 pt, the stroke on screen would keep the detail of a finer Fidelity.
|
||||
if fidelity / zoom > 100.0 {
|
||||
let at_100 = blob_anchors_through(&mut s, command, &extra, Some(100.0), big / zoom);
|
||||
assert!(at_100 > want, "{command} {extra}: {fidelity} px at {zoom}x, 100 pt keeps {at_100} of {want}");
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut p = json!({"points": [[0, 0], [100, 50], [200, 0]], "fidelity": max * 10.0});
|
||||
if let (Some(p), Some(extra)) = (p.as_object_mut(), extra.as_object()) {
|
||||
p.extend(extra.clone());
|
||||
}
|
||||
let err = s.execute(command, &p).unwrap_err().to_string();
|
||||
assert!(err.contains("`fidelity` must be from"), "{command} {extra}: {err}");
|
||||
let default = blob_anchors_through(&mut s, command, &extra, None, 1.0);
|
||||
assert_eq!(default, blob_anchors_through(&mut s, command, &extra, Some(1.5), 1.0), "{command} {extra}");
|
||||
}
|
||||
}
|
||||
|
||||
/// `path.freehand` and `brush.freehand` take a `fidelity` from `MIN_FIT_TOLERANCE` (the lowest
|
||||
/// Fidelity at the highest zoom) to `MAX_FIT_TOLERANCE` (the highest at the lowest zoom), both ends
|
||||
/// included, and reject a value just outside either end, a tenth of the lowest, 0, a negative one or
|
||||
/// a huge one, leaving the document as it was.
|
||||
#[test]
|
||||
fn pencil_takes_fidelity_within_its_range_and_rejects_the_rest() {
|
||||
let (lo, hi) = (vectorcraft_tools::draw2::MIN_FIT_TOLERANCE, vectorcraft_tools::draw2::MAX_FIT_TOLERANCE);
|
||||
let points = json!([[0, 0], [100, 50], [200, 0], [300, 50]]);
|
||||
for (command, extra) in [("path.freehand", json!({})), ("brush.freehand", json!({"style": "brush", "brush": "3 pt. Round"}))] {
|
||||
let run = |s: &mut Session, fidelity: f64| {
|
||||
let mut p = json!({"points": points, "fidelity": fidelity});
|
||||
if let (Some(p), Some(extra)) = (p.as_object_mut(), extra.as_object()) {
|
||||
p.extend(extra.clone());
|
||||
}
|
||||
s.execute(command, &p)
|
||||
};
|
||||
let mut s = session();
|
||||
for fidelity in [100.0 / 0.0313, hi, lo, 0.01 / 640.0] {
|
||||
let r = run(&mut s, fidelity);
|
||||
assert!(r.as_ref().is_ok_and(|r| r["id"].is_u64()), "{command}: {fidelity} pt: {r:?}");
|
||||
}
|
||||
let before = s.doc().unwrap().doc.clone();
|
||||
for fidelity in [f64::from_bits(hi.to_bits() + 1), f64::from_bits(lo.to_bits() - 1), lo / 10.0, 0.0, -1.5, 1e300] {
|
||||
let err = run(&mut s, fidelity).unwrap_err().to_string();
|
||||
assert!(err.contains("`fidelity` must be from"), "{command}: {fidelity} pt: {err}");
|
||||
}
|
||||
assert_eq!(s.doc().unwrap().doc, before, "{command}");
|
||||
}
|
||||
}
|
||||
|
||||
/// A stroke that continues an open path closes it when it ends within four Fidelities of the path's
|
||||
/// other end, at most 400 pt away: at the coarsest Fidelity one ending 1000 pt away leaves it open,
|
||||
/// and one ending 300 pt away closes it.
|
||||
#[test]
|
||||
fn pencil_extend_closes_within_400_pt_at_any_fidelity() {
|
||||
let mut s = session();
|
||||
let max = vectorcraft_tools::draw2::MAX_FIT_TOLERANCE;
|
||||
for (end, closed) in [([0, 1000], false), ([0, 300], true)] {
|
||||
let l = line(&mut s, 0.0, 0.0, 2000.0, 0.0);
|
||||
let points = json!([[2000, 0], [2000, 1000], [1000, 1500], end]);
|
||||
s.execute("path.freehand", &json!({"points": points, "fidelity": max, "extend": {"id": l.0}, "keepSelected": false})).unwrap();
|
||||
assert_eq!(path(&s, l).subpaths[0].closed, closed, "ending at {end:?}");
|
||||
}
|
||||
}
|
||||
|
||||
/// At Fidelity 0.01 short, one-point and repeated-point strokes still draw or are refused, never
|
||||
/// crash, and what they draw has finite anchors.
|
||||
#[test]
|
||||
|
||||
@@ -34,6 +34,11 @@ pub const MIN_FIDELITY: f64 = 0.01;
|
||||
/// Fidelity at the highest zoom (64000 %), whose screen pixels the tools send in points (#5729).
|
||||
pub const MIN_FIT_TOLERANCE: f64 = MIN_FIDELITY / 640.0;
|
||||
|
||||
/// The coarsest tolerance (points) `path.freehand` fits a Pencil or Paintbrush stroke at: the highest
|
||||
/// Fidelity the tools and their Tool Options take (100 px) at the lowest zoom (3.13 %), about 3195 pt.
|
||||
/// A change to either range changes this bound with it.
|
||||
pub const MAX_FIT_TOLERANCE: f64 = 100.0 / 0.0313;
|
||||
|
||||
/// The lowest Fidelity (points) the Smooth tool takes.
|
||||
pub const SMOOTH_MIN_FIDELITY: f64 = 0.05;
|
||||
|
||||
|
||||
@@ -42,6 +42,13 @@ const PREVIEW_FIDELITY_PX: f64 = 0.5;
|
||||
/// The Smooth tool's Fidelity until Smooth Tool Options sets another.
|
||||
const SMOOTH_FIDELITY: f64 = 2.5;
|
||||
|
||||
/// The `fidelity` (points) the Pencil and the Paintbrush send `path.freehand` for `px` screen pixels
|
||||
/// at the current zoom, within the range the command takes
|
||||
/// ([`super::MIN_FIT_TOLERANCE`]..[`super::MAX_FIT_TOLERANCE`]).
|
||||
fn fit_tolerance(cx: &ToolContext, px: f64) -> f64 {
|
||||
cx.tol(px).clamp(super::MIN_FIT_TOLERANCE, super::MAX_FIT_TOLERANCE)
|
||||
}
|
||||
|
||||
pub struct GestureTool {
|
||||
/// How many points the last preview of the stroke had ([`crate::previews_at`]); 0 when it was
|
||||
/// fitted coarser than the stroke kept ([`PREVIEW_FIDELITY_PX`]).
|
||||
@@ -460,7 +467,7 @@ impl GestureTool {
|
||||
pub fn freehand_params(&self, cx: &ToolContext, closed: bool) -> Value {
|
||||
let mut v = json!({
|
||||
"points": self.stroke_json(),
|
||||
"fidelity": cx.tol(self.fidelity),
|
||||
"fidelity": fit_tolerance(cx, self.fidelity),
|
||||
"closed": closed,
|
||||
"style": if self.id == "paintbrush" { "brush" } else { "pencil" },
|
||||
"fill": self.fill,
|
||||
@@ -654,7 +661,7 @@ impl Tool for GestureTool {
|
||||
if self.previews() && crate::previews_at(self.points.len()) {
|
||||
let mut v = self.freehand_params(cx, false);
|
||||
let fidelity = self.fidelity.max(PREVIEW_FIDELITY_PX);
|
||||
v["fidelity"] = json!(cx.tol(fidelity));
|
||||
v["fidelity"] = json!(fit_tolerance(cx, fidelity));
|
||||
// A preview fitted coarser than the stroke kept is stale (`deactivate`).
|
||||
self.previewed = if fidelity > self.fidelity { 0 } else { self.points.len() };
|
||||
if !self.live_fit {
|
||||
|
||||
@@ -215,6 +215,52 @@ fn fidelity_is_in_screen_pixels_at_any_zoom() {
|
||||
}
|
||||
}
|
||||
|
||||
/// The highest Fidelity (100 px) at the lowest zoom (3.13 %) is sent, while the stroke is drawn and
|
||||
/// at its release, as [`crate::draw2::MAX_FIT_TOLERANCE`], the coarsest tolerance `path.freehand`
|
||||
/// reads.
|
||||
#[test]
|
||||
fn highest_fidelity_at_the_lowest_zoom_sends_the_coarsest_tolerance() {
|
||||
let (d, _) = doc_with_rect();
|
||||
let s = Selection::default();
|
||||
let p = paint();
|
||||
let mut cx = cx(&d, &s, &p);
|
||||
cx.zoom = 0.0313;
|
||||
let stroke: Vec<_> = [(0.0, 0.0), (40.0, 20.0), (80.0, 0.0), (120.0, 20.0)].iter().map(|&(x, y)| (x / cx.zoom, y / cx.zoom)).collect();
|
||||
for id in ["pencil", "paintbrush"] {
|
||||
let mut t = GestureTool::new(id);
|
||||
t.set_option("fidelity", &json!(100.0));
|
||||
let acts = drag(&mut t, &cx, &stroke, Mods::default());
|
||||
let sent: Vec<_> =
|
||||
acts.concat().into_iter().filter_map(|a| if let Action::Preview(_, v) = a { v["fidelity"].as_f64() } else { None }).collect();
|
||||
assert_eq!(sent, [crate::draw2::MAX_FIT_TOLERANCE; 4], "{id}");
|
||||
}
|
||||
}
|
||||
|
||||
/// `path.freehand` rejects a `fidelity` outside `MIN_FIT_TOLERANCE..=MAX_FIT_TOLERANCE`, so at a zoom
|
||||
/// beyond the view's range the Pencil and the Paintbrush send the nearer end of it, while the stroke
|
||||
/// is drawn and at its release.
|
||||
#[test]
|
||||
fn fidelity_sent_stays_within_what_path_freehand_takes() {
|
||||
let (d, _) = doc_with_rect();
|
||||
let s = Selection::default();
|
||||
let p = paint();
|
||||
let mut cx = cx(&d, &s, &p);
|
||||
let (lo, hi) = (crate::draw2::MIN_FIT_TOLERANCE, crate::draw2::MAX_FIT_TOLERANCE);
|
||||
for (zoom, fidelity, release) in [(0.01, 100.0, hi), (1000.0, 0.01, lo)] {
|
||||
cx.zoom = zoom;
|
||||
let stroke: Vec<_> = [(0.0, 0.0), (40.0, 20.0), (80.0, 0.0), (120.0, 20.0)].iter().map(|&(x, y)| (x / zoom, y / zoom)).collect();
|
||||
for id in ["pencil", "paintbrush"] {
|
||||
let mut t = GestureTool::new(id);
|
||||
t.set_option("fidelity", &json!(fidelity));
|
||||
let acts = drag(&mut t, &cx, &stroke, Mods::default());
|
||||
let sent: Vec<_> =
|
||||
acts.concat().into_iter().filter_map(|a| if let Action::Preview(_, v) = a { v["fidelity"].as_f64() } else { None }).collect();
|
||||
assert!(sent.iter().all(|f| (lo..=hi).contains(f)), "{id} at {zoom}x: {sent:?}");
|
||||
assert_eq!(sent.last(), Some(&release), "{id} at {zoom}x: {sent:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The Blob Brush's Fidelity stays in points (#5729 is the Pencil's and the Paintbrush's): its
|
||||
/// stroke, and another tool chosen while it is drawn, send it as set at any zoom.
|
||||
#[test]
|
||||
|
||||
@@ -2916,6 +2916,13 @@ already walks Image Trace. Short agent entry points:
|
||||
Tilt goes from its first value with the pen lying flat to its second upright, and on Bearing or Rotation from its
|
||||
first value at −180° to its second at 180°. A stroke without that input draws the brush's own value (a Scatter
|
||||
brush's value half way between the two).
|
||||
- Freehand Fidelity: the `fidelity` of `path.freehand` and `brush.freehand` is in points, 1.5 pt when left out. A
|
||||
value must be from 0.000015625 pt (0.01 / 640) to 100 / 0.0313 pt (about 3194.89 pt), and a value outside that range
|
||||
is rejected with an error. The Pencil and the Paintbrush give Fidelity in screen pixels, 0.01 to 100 px, and send it
|
||||
divided by the zoom (0.0313 to 640) and kept within that range, so a stroke is fitted at the same tolerance on screen
|
||||
at every zoom. A stroke
|
||||
that continues a path (`extend`) closes it when it ends within four times the Fidelity of the path's other end, at
|
||||
least 6 pt and at most 400 pt. The Blob Brush's `path.blob` Fidelity stays in points, 0.01 to 100 pt.
|
||||
- Variables: `variable.list`, `variable.define {name, kind: text|visibility|image|graph}`, `variable.bind {variable, ids?}`,
|
||||
`variable.makeTextDynamic`, `variable.makeVisibilityDynamic`, `variable.makeImageDynamic`, `variable.makeGraphDynamic`
|
||||
(`variable.bind` and `variable.makeTextDynamic` refuse type that already has another text variable and name it),
|
||||
|
||||
@@ -1086,6 +1086,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
|
||||
Live curve fitting in the Pencil Tool Options is on by default; while it is off, the path shown while a stroke is drawn joins the stroke's points with straight segments, and the release fits the curve, as [Pencil Tool Options](https://web.archive.org/web/20261002104400/https://helpx.adobe.com/illustrator/desktop/draw-shapes-and-paths/draw-shapes/pencil-tool-options.html) describes (`path.freehand` `fit`).
|
||||
In the Pencil and Paintbrush Tool Options, Edit selected paths within 0 px turns editing off, so a stroke that starts exactly on an end of a selected open path draws a new path; Close paths when ends are within 0 px turns closing off, so a stroke that ends exactly where it started stays open.
|
||||
The Pencil's and the Paintbrush's Fidelity is in screen pixels, the same at any zoom: their Tool Options give it in px, from 0.01 px on the logarithmic slider to 20 px and up to 100 px in the field, and the tool sends `path.freehand` the value in points at the current zoom (#5729). The Smooth tool's and the Blob Brush's Fidelity stays in points.
|
||||
`path.freehand` and `brush.freehand` take a Fidelity of up to 100 / 0.0313 pt (about 3194.89 pt), the 100 px field maximum at the lowest zoom (3.13 %), and reject a value outside their range with an error, so a Pencil or Paintbrush stroke drawn zoomed out keeps the Fidelity set in pixels (engine tests `pencil_fit_keeps_its_anchors_on_a_stroke_drawn_zoomed_out` and `pencil_takes_fidelity_within_its_range_and_rejects_the_rest`).
|
||||
Before a press that draws a new path, the Pencil cursor shows a small star, as [Draw freeform paths with the Pencil tool](https://web.archive.org/web/20260926095638/https://helpx.adobe.com/illustrator/desktop/draw-shapes-and-paths/draw-shapes/draw-freeform-paths-with-the-pencil-tool.html) describes; over a selected path within Edit selected paths' distance, which the press continues from an end or redraws, the star is not shown ([Reshape paths with the Pencil tool](https://web.archive.org/web/20260106004407/https://helpx.adobe.com/illustrator/desktop/draw-shapes-and-paths/draw-shapes/reshape-paths-with-the-pencil-tool.html)).
|
||||
Keep selected in the Pencil and Paintbrush Tool Options is on by default: while it is on, a stroke selects the path it draws or continues and replaces the earlier selection. While it is off, a new stroke leaves nothing selected, the earlier selection included, as the reference application starts the stroke by dropping it (#5752); a continued or redrawn path is deselected while the rest of the selection stays (`path.freehand` `keepSelected`).
|
||||
With two open paths selected, a Pencil stroke from an end of one that ends within Edit selected paths' distance of an end of the other connects them into one path with the first path's appearance, in one undo step, as [Connect two paths with the Pencil tool](https://helpx.adobe.com/illustrator/desktop/draw-shapes-and-paths/draw-shapes/connect-two-paths-with-the-pencil-tool.html) describes (`path.freehand` `connect`).
|
||||
|
||||
Reference in New Issue
Block a user