Files
vectorcraft/crates/tools/src/addons.rs
T
dotCooCoo 3a336fd32f Path Precision Tools: Trim at Crossings removes the stretch of a path between crossings
The new Trim at Crossings Tool removes stretches of paths between the
places where they are crossed by another path or by themselves. A click
removes the stretch under the pointer, a drag removes every stretch its
rectangle touches, and an Alt-drag removes every stretch its freehand
line crosses. It works on every path that can be edited, selected or
not, and each gesture is one undo step.

- `addon.pathtools.trim {at, tolerance?}`, `{rect}` or `{points}` is the
  command behind the tool. A closed path that loses a stretch becomes
  open, an open path that loses a middle stretch becomes two paths with
  its appearance in its place in the stacking order, and a path that
  nothing crosses is removed whole. The stretches that stay get no new
  anchors.
- Hidden and locked paths, guides and clipping paths are not trimmed and
  do not count as crossing. Only the paths the gesture reaches are
  searched for crossings, and one work limit covers every path the
  command trims.
- `crossings_on` and `crossings_on_within` in `vectorcraft-pathops` find
  the crossings on one path and compare it only with the segments in
  its box.
- The new tool shares the crossing search with Add Points at Crossings
  and Split at Crossings, so those change too: two segments of a path
  that meet at an anchor, directly or across segments with no length,
  now count as crossing where they cross again away from that anchor,
  and do not count as crossing at the anchor itself. With Self
  Crossings, a curved segment that loops over itself counts as crossing
  itself. Before, both crossings were missed.
- The tool is an add-on tool: Object › Plug-ins › Path Precision Tools
  and the All Tools drawer list it, it has no shortcut, and neither
  shipped toolbar shows it.

Docs: docs/mcp.md, docs/ui-parity.md, docs/target-app-parity.md.
2026-11-13 09:24:53 -08:00

96 lines
4.9 KiB
Rust

//! The tools of VectorCraft's built-in add-ons. They aren't in the toolbars as they ship: the All
//! Tools drawer lists them (under their add-on's heading) so they can be added, and each add-on's
//! submenu selects them. Tool gestures emit engine commands.
pub mod anchor_eraser;
pub mod box3d;
pub mod contour;
pub mod corner_tool;
pub mod enclosing_lasso;
pub mod extend_path;
pub mod measure_inspect;
pub mod parametric_shape;
pub mod shared_edge;
pub mod slide_anchor;
pub mod smart_pen;
pub mod trim;
use crate::{Tool, ToolInfo};
/// Path Precision Tools' tools, one flyout group.
pub const PATH_TOOLS: &[ToolInfo] = &[
ToolInfo { id: anchor_eraser::ID, label: "Anchor Eraser Brush", shortcut: None, icon: "dc-remove-brush" },
ToolInfo { id: extend_path::ID, label: "Extend Path Tool", shortcut: None, icon: "dc-arrow-extend" },
ToolInfo { id: measure_inspect::ID, label: "Measure & Inspect Tool", shortcut: None, icon: "ruler" },
ToolInfo { id: parametric_shape::ID, label: "Parametric Shape Tool", shortcut: None, icon: "hexagon" },
ToolInfo { id: corner_tool::ID, label: "Corner Tool", shortcut: None, icon: "dc-rounded-rect" },
ToolInfo { id: slide_anchor::ID, label: "Slide Anchor Along Path", shortcut: None, icon: "dc-anchor" },
ToolInfo { id: shared_edge::ID, label: "Shared Edge Merge Tool", shortcut: None, icon: "dc-join" },
ToolInfo { id: trim::ID, label: "Trim at Crossings Tool", shortcut: None, icon: "dc-pf-trim" },
];
/// Smart Pen's tool, a flyout group of its own.
pub const SMART_PEN: &[ToolInfo] = &[ToolInfo { id: smart_pen::ID, label: "Smart Pen Tool", shortcut: None, icon: "spline-pointer" }];
pub const CONTOUR: &[ToolInfo] = &[ToolInfo { id: contour::ID, label: "Contour Tool", shortcut: None, icon: "tool-contour" }];
pub const ENCLOSING_LASSO: &[ToolInfo] = &[ToolInfo { id: enclosing_lasso::ID, label: "Enclosing Lasso Tool", shortcut: None, icon: "tool-lasso" }];
pub const BOX_3D: &[ToolInfo] = &[ToolInfo { id: box3d::ID, label: "3D Box Tool", shortcut: None, icon: "box" }];
/// The add-ons' tool groups, after the toolbar's own ([`crate::TOOL_GROUPS`]), each with the
/// heading the All Tools drawer lists it under (its add-on's name).
const HEADED: &[(&[ToolInfo], &str)] =
&[(PATH_TOOLS, "Path Precision Tools"), (SMART_PEN, "Smart Pen"), (ENCLOSING_LASSO, "Enclosing Lasso"), (CONTOUR, "Contour"), (BOX_3D, "3D Box")];
/// The add-ons' tool groups, after the toolbar's own ([`crate::TOOL_GROUPS`]).
pub const GROUPS: &[&[ToolInfo]] = &[PATH_TOOLS, SMART_PEN, ENCLOSING_LASSO, CONTOUR, BOX_3D];
/// The heading the All Tools drawer lists tool `id` under: its add-on's name.
pub fn heading(id: &str) -> Option<&'static str> {
HEADED.iter().find(|(g, _)| g.iter().any(|t| t.id == id)).map(|(_, h)| *h)
}
/// Create an add-on tool by id (None = not one of them).
pub fn create(id: &str) -> Option<Box<dyn Tool>> {
Some(match id {
anchor_eraser::ID => Box::new(anchor_eraser::AnchorEraserTool::default()),
corner_tool::ID => Box::new(corner_tool::CornerTool::default()),
extend_path::ID => Box::new(extend_path::ExtendPathTool::default()),
measure_inspect::ID => Box::new(measure_inspect::MeasureInspectTool::default()),
parametric_shape::ID => Box::new(parametric_shape::ParametricShapeTool::default()),
shared_edge::ID => Box::new(shared_edge::SharedEdgeTool::default()),
slide_anchor::ID => Box::new(slide_anchor::SlideAnchorTool::default()),
smart_pen::ID => Box::new(smart_pen::SmartPenTool::default()),
trim::ID => Box::new(trim::TrimTool::default()),
enclosing_lasso::ID => Box::new(enclosing_lasso::EnclosingLassoTool::default()),
contour::ID => Box::new(contour::ContourTool::default()),
box3d::ID => Box::new(box3d::Box3dTool::default()),
_ => return None,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_addon_tool_is_made_and_catalogued_but_not_in_the_toolbar() {
for t in GROUPS.iter().flat_map(|g| g.iter()) {
assert_eq!(create(t.id).map(|tool| tool.id()), Some(t.id));
assert_eq!(crate::tool_info(t.id).map(|i| i.label), Some(t.label));
assert!(crate::create(t.id).id() == t.id);
assert!(t.shortcut.is_none(), "no default shortcut");
assert!(!crate::TOOL_GROUPS.iter().any(|g| g.iter().any(|x| x.id == t.id)), "{} is not in the toolbar's groups", t.id);
}
assert!(PATH_TOOLS.iter().all(|t| heading(t.id) == Some("Path Precision Tools")));
assert_eq!(heading(smart_pen::ID), Some("Smart Pen"));
assert_eq!(heading(enclosing_lasso::ID), Some("Enclosing Lasso"));
assert_eq!(heading(contour::ID), Some("Contour"));
assert_eq!(heading(box3d::ID), Some("3D Box"));
assert_eq!(GROUPS.len(), HEADED.len());
assert!(crate::catalog::all_tools().any(|t| t.id == extend_path::ID));
assert!(create("pen").is_none() && heading("pen").is_none());
}
}