mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 14:18:04 +08:00
Port all remaining Subpath producers to BezPath and delete the legacy subpath module (#4457)
* Port all remaining Subpath producers to BezPath and delete the legacy subpath module * Reset contour state at each MoveTo so an open contour's segments don't leak into the next region * Give the polygon and star constructors a true center and radius instead of compensated arguments
This commit is contained in:
@@ -12,8 +12,7 @@ use graph_craft::application_io::resource::{DataSource, ResourceHash};
|
||||
use graph_craft::document::NodeId;
|
||||
use graphene_std::Color;
|
||||
use graphene_std::raster::Image;
|
||||
use graphene_std::subpath::BezierHandles;
|
||||
use graphene_std::vector::misc::{HandleId, point_to_dvec2, segment_to_handles};
|
||||
use graphene_std::vector::misc::{BezierHandles, HandleId, point_to_dvec2, segment_to_handles};
|
||||
use graphene_std::vector::{PointId, SegmentId, VectorModificationType};
|
||||
use graphite_proc_macros::{ExtractField, message_handler_data};
|
||||
use kurbo::ParamCurve;
|
||||
|
||||
@@ -18,12 +18,12 @@ use graphene_std::raster::{
|
||||
CellularDistanceFunction, CellularReturnType, DomainWarpType, FractalType, LuminanceCalculation, NoiseType, RedGreenBlue, RedGreenBlueAlpha, RelativeAbsolute, SelectiveColorChoice,
|
||||
};
|
||||
use graphene_std::raster_types::{CPU, GPU, Raster};
|
||||
use graphene_std::subpath::BezierHandles;
|
||||
use graphene_std::text::TextAlign;
|
||||
use graphene_std::text_nodes::StringCapitalization;
|
||||
use graphene_std::transform::{ReferencePoint, ScaleType};
|
||||
use graphene_std::vector::misc::{
|
||||
ArcType, BooleanOperation, BoxCorners, CentroidType, ExtrudeJoiningAlgorithm, GridType, InterpolationDistribution, MergeByDistanceAlgorithm, PointSpacingType, RowsOrColumns, SpiralType,
|
||||
ArcType, BezierHandles, BooleanOperation, BoxCorners, CentroidType, ExtrudeJoiningAlgorithm, GridType, InterpolationDistribution, MergeByDistanceAlgorithm, PointSpacingType, RowsOrColumns,
|
||||
SpiralType,
|
||||
};
|
||||
use graphene_std::vector::style::{
|
||||
DashPattern, FillChoice, GradientForm, GradientHueDirection, GradientInterpolation, GradientRamp, GradientSettings, GradientSpace, GradientSpread, StrokeAlign, StrokeCap, StrokeJoin,
|
||||
|
||||
@@ -41,13 +41,12 @@ use graphene_std::Cover;
|
||||
use graphene_std::math::quad::Quad;
|
||||
use graphene_std::path_bool_nodes::boolean_intersect;
|
||||
use graphene_std::raster::BlendMode;
|
||||
use graphene_std::subpath::Subpath;
|
||||
use graphene_std::vector::algorithms::bezpath_algorithms::bezpath_is_inside_bezpath;
|
||||
use graphene_std::vector::click_target::{ClickTarget, ClickTargetType};
|
||||
use graphene_std::vector::graphic_types;
|
||||
use graphene_std::vector::misc::dvec2_to_point;
|
||||
use graphene_std::vector::style::RenderMode;
|
||||
use graphene_std::vector::{PointId, graphic_types};
|
||||
use kurbo::{Affine, BezPath, Line, PathSeg};
|
||||
use kurbo::{Affine, BezPath, Line, PathSeg, Shape};
|
||||
use std::collections::HashSet;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
@@ -1832,17 +1831,16 @@ impl DocumentMessageHandler {
|
||||
self.intersect_quad(viewport_quad, viewport).filter(|layer| !self.network_interface.is_artboard(&layer.to_node(), &[]))
|
||||
}
|
||||
|
||||
/// Runs an intersection test with all layers and a viewport space subpath
|
||||
pub fn intersect_polygon<'a>(&'a self, mut viewport_polygon: Subpath<PointId>, viewport: &ViewportMessageHandler) -> impl Iterator<Item = LayerNodeIdentifier> + use<'a> {
|
||||
/// Runs an intersection test with all layers and a viewport space polygon path
|
||||
pub fn intersect_polygon<'a>(&'a self, mut viewport_polygon: BezPath, viewport: &ViewportMessageHandler) -> impl Iterator<Item = LayerNodeIdentifier> + use<'a> {
|
||||
let document_to_viewport = self.navigation_handler.calculate_offset_transform(viewport.center_in_viewport_space().into(), &self.document_ptz);
|
||||
viewport_polygon.apply_transform(document_to_viewport.inverse());
|
||||
viewport_polygon.apply_affine(Affine::new(document_to_viewport.inverse().to_cols_array()));
|
||||
|
||||
let polygon = BezPath::from_path_segments(viewport_polygon.iter_closed());
|
||||
ClickXRayIter::new(&self.network_interface, XRayTarget::Path(polygon))
|
||||
ClickXRayIter::new(&self.network_interface, XRayTarget::Path(viewport_polygon))
|
||||
}
|
||||
|
||||
/// Runs an intersection test with all layers and a viewport space subpath; ignoring artboards
|
||||
pub fn intersect_polygon_no_artboards<'a>(&'a self, viewport_polygon: Subpath<PointId>, viewport: &ViewportMessageHandler) -> impl Iterator<Item = LayerNodeIdentifier> + use<'a> {
|
||||
/// Runs an intersection test with all layers and a viewport space polygon path; ignoring artboards
|
||||
pub fn intersect_polygon_no_artboards<'a>(&'a self, viewport_polygon: BezPath, viewport: &ViewportMessageHandler) -> impl Iterator<Item = LayerNodeIdentifier> + use<'a> {
|
||||
self.intersect_polygon(viewport_polygon, viewport)
|
||||
.filter(|layer| !self.network_interface.is_artboard(&layer.to_node(), &[]))
|
||||
}
|
||||
@@ -1870,26 +1868,24 @@ impl DocumentMessageHandler {
|
||||
layer_left >= quad_left && layer_right <= quad_right && layer_top <= quad_top && layer_bottom >= quad_bottom
|
||||
}
|
||||
|
||||
pub fn is_layer_fully_inside_polygon(&self, layer: &LayerNodeIdentifier, viewport: &ViewportMessageHandler, mut viewport_polygon: Subpath<PointId>) -> bool {
|
||||
pub fn is_layer_fully_inside_polygon(&self, layer: &LayerNodeIdentifier, viewport: &ViewportMessageHandler, mut viewport_polygon: BezPath) -> bool {
|
||||
let document_to_viewport = self.navigation_handler.calculate_offset_transform(viewport.center_in_viewport_space().into(), &self.document_ptz);
|
||||
viewport_polygon.apply_transform(document_to_viewport.inverse());
|
||||
viewport_polygon.apply_affine(Affine::new(document_to_viewport.inverse().to_cols_array()));
|
||||
|
||||
let layer_click_targets = self.network_interface.document_metadata().click_targets(*layer);
|
||||
let layer_transform = self.network_interface.document_metadata().transform_to_document(*layer);
|
||||
|
||||
let viewport_polygon_bezpath = viewport_polygon.to_bezpath();
|
||||
|
||||
layer_click_targets.is_some_and(|targets| {
|
||||
targets.iter().all(|target| match target.target_type() {
|
||||
ClickTargetType::Path(path) => {
|
||||
let mut path = path.clone();
|
||||
path.apply_affine(Affine::new(layer_transform.to_cols_array()));
|
||||
bezpath_is_inside_bezpath(&path, &viewport_polygon_bezpath, None, None)
|
||||
bezpath_is_inside_bezpath(&path, &viewport_polygon, None, None)
|
||||
}
|
||||
ClickTargetType::FreePoint(point) => {
|
||||
let mut point = *point;
|
||||
point.apply_transform(layer_transform);
|
||||
viewport_polygon.contains_point(point.position)
|
||||
viewport_polygon.contains(dvec2_to_point(point.position))
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -849,13 +849,13 @@ fn import_usvg_node_inner(
|
||||
|
||||
/// Helper to apply path data (vector geometry, fill, stroke, transform) to a layer.
|
||||
fn import_usvg_path(modify_inputs: &mut ModifyInputsContext, node: &usvg::Node, path: &usvg::Path, layer: LayerNodeIdentifier, gradient_info: &SvgGradientInfo) {
|
||||
let subpaths = convert_usvg_path(path);
|
||||
let bezpath = convert_usvg_path(path);
|
||||
|
||||
// Skip creating a Transform node entirely when the SVG-native transform is identity.
|
||||
let node_transform = usvg_transform(node.abs_transform());
|
||||
let has_transform = node_transform != DAffine2::IDENTITY;
|
||||
|
||||
modify_inputs.insert_vector(subpaths, layer, has_transform, path.fill().is_some(), path.stroke().is_some());
|
||||
modify_inputs.insert_vector(bezpath, layer, has_transform, path.fill().is_some(), path.stroke().is_some());
|
||||
|
||||
if has_transform && let Some(transform_node_id) = modify_inputs.existing_proto_node_id(graphene_std::transform_nodes::transform::IDENTIFIER, false) {
|
||||
transform_utils::update_transform(modify_inputs.network_interface, &transform_node_id, node_transform);
|
||||
|
||||
@@ -2,9 +2,7 @@ use crate::messages::portfolio::document::utility_types::network_interface::{Inp
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{NodeId, NodeInput};
|
||||
use graphene_std::subpath::Subpath;
|
||||
use graphene_std::transform::Transform;
|
||||
use graphene_std::vector::PointId;
|
||||
|
||||
/// Update the inputs of the transform node to match a new transform
|
||||
pub fn update_transform(network_interface: &mut NodeNetworkInterface, node_id: &NodeId, transform: DAffine2) {
|
||||
@@ -87,32 +85,6 @@ pub fn get_current_normalized_pivot(inputs: &[NodeInput]) -> DVec2 {
|
||||
if let Some(&TaggedValue::DVec2(pivot)) = inputs[5].as_value() { pivot } else { DVec2::splat(0.5) }
|
||||
}
|
||||
|
||||
/// Expand a bounds to avoid div zero errors
|
||||
fn clamp_bounds(bounds_min: DVec2, mut bounds_max: DVec2) -> [DVec2; 2] {
|
||||
let bounds_size = bounds_max - bounds_min;
|
||||
if bounds_size.x < 1e-10 {
|
||||
bounds_max.x = bounds_min.x + 1.;
|
||||
}
|
||||
if bounds_size.y < 1e-10 {
|
||||
bounds_max.y = bounds_min.y + 1.;
|
||||
}
|
||||
[bounds_min, bounds_max]
|
||||
}
|
||||
/// Returns corners of all subpaths
|
||||
fn subpath_bounds(subpaths: &[Subpath<PointId>]) -> [DVec2; 2] {
|
||||
subpaths
|
||||
.iter()
|
||||
.filter_map(|subpath| subpath.bounding_box())
|
||||
.reduce(|b1, b2| [b1[0].min(b2[0]), b1[1].max(b2[1])])
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Returns corners of all subpaths (but expanded to avoid division-by-zero errors)
|
||||
pub fn nonzero_subpath_bounds(subpaths: &[Subpath<PointId>]) -> [DVec2; 2] {
|
||||
let [bounds_min, bounds_max] = subpath_bounds(subpaths);
|
||||
clamp_bounds(bounds_min, bounds_max)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -16,11 +16,11 @@ use graph_craft::{ProtoNodeIdentifier, list};
|
||||
use graphene_std::brush::brush_stroke::BrushStroke;
|
||||
use graphene_std::raster::BlendMode;
|
||||
use graphene_std::raster_types::Image;
|
||||
use graphene_std::subpath::Subpath;
|
||||
use graphene_std::text::{Font, TypesettingConfig};
|
||||
use graphene_std::vector::style::{GradientForm, GradientHueDirection, GradientInterpolation, GradientSettings, GradientSpace, GradientSpread, PaintOrder, Stroke};
|
||||
use graphene_std::vector::{Gradient, GradientRamp, PointId, Vector, VectorModification, VectorModificationType};
|
||||
use graphene_std::vector::{Gradient, GradientRamp, Vector, VectorModification, VectorModificationType};
|
||||
use graphene_std::{Artboard, Color, Graphic};
|
||||
use kurbo::BezPath;
|
||||
|
||||
#[derive(PartialEq, Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub enum TransformIn {
|
||||
@@ -146,9 +146,9 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
path_id
|
||||
}
|
||||
|
||||
pub fn insert_vector(&mut self, subpaths: Vec<Subpath<PointId>>, layer: LayerNodeIdentifier, include_transform: bool, include_fill: bool, include_stroke: bool) {
|
||||
pub fn insert_vector(&mut self, bezpath: BezPath, layer: LayerNodeIdentifier, include_transform: bool, include_fill: bool, include_stroke: bool) {
|
||||
// Build a VectorModification that reproduces the geometry (same format the Pen tool uses)
|
||||
let vector = Vector::from_subpaths(subpaths, true);
|
||||
let vector = Vector::from_bezpath(bezpath);
|
||||
let modification = Box::new(VectorModification::create_from_vector(&vector));
|
||||
|
||||
let shape = resolve_network_node_type("Path")
|
||||
|
||||
@@ -6,8 +6,7 @@ pub use crate::messages::portfolio::document::utility_types::text_metrics::text_
|
||||
use crate::messages::tool::common_functionality::shape_editor::{SelectedLayerState, ShapeState};
|
||||
use crate::messages::tool::tool_messages::tool_prelude::DocumentMessageHandler;
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene_std::subpath::BezierHandles;
|
||||
use graphene_std::vector::misc::{ManipulatorPointId, point_to_dvec2, segment_to_handles};
|
||||
use graphene_std::vector::misc::{BezierHandles, ManipulatorPointId, point_to_dvec2, segment_to_handles};
|
||||
use graphene_std::vector::{PointId, SegmentId, Vector};
|
||||
use kurbo::{Affine, ParamCurve, PathSeg};
|
||||
use std::collections::HashMap;
|
||||
|
||||
@@ -39,8 +39,8 @@ impl NodeNetworkInterface {
|
||||
|
||||
let mut targets = Vec::new();
|
||||
let mut combined = BezPath::new();
|
||||
for subpath in vector.stroke_bezier_paths() {
|
||||
combined.extend(subpath.to_bezpath().elements().iter().copied());
|
||||
for bezpath in vector.stroke_bezpath_iter() {
|
||||
combined.extend(bezpath.elements().iter().copied());
|
||||
}
|
||||
if !combined.is_empty() {
|
||||
targets.push(ClickTargetType::Path(combined));
|
||||
|
||||
@@ -12,7 +12,7 @@ use crate::messages::tool::common_functionality::shapes::spiral_shape::calculate
|
||||
use glam::DVec2;
|
||||
use graph_craft::document::NodeInput;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graphene_std::subpath::{calculate_growth_factor, spiral_point};
|
||||
use graphene_std::vector::algorithms::shapes::{calculate_growth_factor, spiral_point};
|
||||
use graphene_std::vector::misc::SpiralType;
|
||||
use std::collections::VecDeque;
|
||||
use std::f64::consts::TAU;
|
||||
|
||||
@@ -12,10 +12,9 @@ use crate::messages::tool::common_functionality::snapping::SnapTypeConfiguration
|
||||
use crate::messages::tool::common_functionality::utility_functions::is_visible_point;
|
||||
use crate::messages::tool::tool_messages::path_tool::{PathOverlayMode, PointSelectState};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene_std::subpath::{BezierHandles, Subpath};
|
||||
use graphene_std::subpath::{PathSegPoints, pathseg_points};
|
||||
use graphene_std::vector::algorithms::bezpath_algorithms::pathseg_compute_lookup_table;
|
||||
use graphene_std::vector::misc::{HandleId, ManipulatorPointId, dvec2_to_point, point_to_dvec2, segment_to_handles};
|
||||
use graphene_std::vector::algorithms::shapes::polyline_bezpath;
|
||||
use graphene_std::vector::misc::{BezierHandles, HandleId, ManipulatorPointId, PathSegPoints, bezpath_from_manipulator_groups, dvec2_to_point, pathseg_points, point_to_dvec2, segment_to_handles};
|
||||
use graphene_std::vector::{HandleExt, PointId, SegmentId, Vector, VectorModificationType};
|
||||
use kurbo::{Affine, DEFAULT_ACCURACY, Line, ParamCurve, ParamCurveNearest, PathSeg, Rect, Shape};
|
||||
use std::f64::consts::TAU;
|
||||
@@ -750,9 +749,9 @@ impl ShapeState {
|
||||
|
||||
let mut selected_stack = Vec::new();
|
||||
// Find all subpaths that have been clicked
|
||||
for stroke in vector.stroke_bezier_paths() {
|
||||
if stroke.contains_point(layer_mouse)
|
||||
&& let Some(first) = stroke.manipulator_groups().first()
|
||||
for (groups, closed) in vector.stroke_manipulator_groups() {
|
||||
if bezpath_from_manipulator_groups(&groups, closed).contains(dvec2_to_point(layer_mouse))
|
||||
&& let Some(first) = groups.first()
|
||||
{
|
||||
selected_stack.push(first.id);
|
||||
}
|
||||
@@ -2223,12 +2222,11 @@ impl ShapeState {
|
||||
assert!(vector.point_domain.ids().contains(&end));
|
||||
}
|
||||
|
||||
let polygon_subpath = if let SelectionShape::Lasso(polygon) = selection_shape {
|
||||
let polygon_bezpath = if let SelectionShape::Lasso(polygon) = selection_shape {
|
||||
if polygon.len() < 2 {
|
||||
return (points_inside, segments_inside);
|
||||
}
|
||||
let polygon: Subpath<PointId> = Subpath::from_anchors(polygon.to_vec(), true);
|
||||
Some(polygon)
|
||||
Some(polyline_bezpath(polygon.iter().copied(), true))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -2256,13 +2254,13 @@ impl ShapeState {
|
||||
}
|
||||
}
|
||||
SelectionShape::Lasso(_) => {
|
||||
let polygon = polygon_subpath.as_ref().expect("If `selection_shape` is a polygon then subpath is constructed beforehand.");
|
||||
let polygon = polygon_bezpath.as_ref().expect("If `selection_shape` is a polygon then its path is constructed beforehand.");
|
||||
|
||||
// Sample 10 points on the bezier and check if all or some lie inside the polygon
|
||||
let points = pathseg_compute_lookup_table(segment, Some(10), false);
|
||||
match selection_mode {
|
||||
SelectionMode::Enclosed => points.map(|p| transform.transform_point2(p)).all(|p| polygon.contains_point(p)),
|
||||
_ => points.map(|p| transform.transform_point2(p)).any(|p| polygon.contains_point(p)),
|
||||
SelectionMode::Enclosed => points.map(|p| transform.transform_point2(p)).all(|p| polygon.contains(dvec2_to_point(p))),
|
||||
_ => points.map(|p| transform.transform_point2(p)).any(|p| polygon.contains(dvec2_to_point(p))),
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -2281,10 +2279,10 @@ impl ShapeState {
|
||||
|
||||
let select = match selection_shape {
|
||||
SelectionShape::Box(rect) => rect.contains(dvec2_to_point(transformed_position)),
|
||||
SelectionShape::Lasso(_) => polygon_subpath
|
||||
SelectionShape::Lasso(_) => polygon_bezpath
|
||||
.as_ref()
|
||||
.expect("If `selection_shape` is a polygon then subpath is constructed beforehand.")
|
||||
.contains_point(transformed_position),
|
||||
.expect("If `selection_shape` is a polygon then its path is constructed beforehand.")
|
||||
.contains(dvec2_to_point(transformed_position)),
|
||||
};
|
||||
|
||||
if select && select_points {
|
||||
@@ -2306,10 +2304,10 @@ impl ShapeState {
|
||||
|
||||
let select = match selection_shape {
|
||||
SelectionShape::Box(rect) => rect.contains(dvec2_to_point(transformed_position)),
|
||||
SelectionShape::Lasso(_) => polygon_subpath
|
||||
SelectionShape::Lasso(_) => polygon_bezpath
|
||||
.as_ref()
|
||||
.expect("If `selection_shape` is a polygon then subpath is constructed beforehand.")
|
||||
.contains_point(transformed_position),
|
||||
.expect("If `selection_shape` is a polygon then its path is constructed beforehand.")
|
||||
.contains(dvec2_to_point(transformed_position)),
|
||||
};
|
||||
|
||||
if select && select_points {
|
||||
|
||||
@@ -16,8 +16,7 @@ use crate::messages::tool::utility_types::*;
|
||||
use glam::{DAffine2, DMat2, DVec2};
|
||||
use graph_craft::document::NodeInput;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graphene_std::subpath::Subpath;
|
||||
use graphene_std::vector::PointId;
|
||||
use graphene_std::vector::algorithms::shapes::{arc_bezpath, regular_polygon_bezpath, star_polygon_bezpath};
|
||||
use graphene_std::vector::click_target::ClickTargetType;
|
||||
use graphene_std::vector::misc::{ArcType, GridType, SpiralType, dvec2_to_point};
|
||||
use kurbo::{BezPath, PathEl, Shape};
|
||||
@@ -443,12 +442,7 @@ pub fn star_outline(layer: Option<LayerNodeIdentifier>, document: &DocumentMessa
|
||||
let viewport = document.metadata().transform_to_viewport(layer);
|
||||
|
||||
let points = sides as u64;
|
||||
let diameter: f64 = radius1 * 2.;
|
||||
let inner_diameter = radius2 * 2.;
|
||||
|
||||
let targets: Vec<ClickTargetType> = vec![ClickTargetType::Path(
|
||||
Subpath::<PointId>::new_star_polygon(DVec2::splat(-diameter), points, diameter, inner_diameter).to_bezpath(),
|
||||
)];
|
||||
let targets: Vec<ClickTargetType> = vec![ClickTargetType::Path(star_polygon_bezpath(DVec2::ZERO, points, radius1, radius2))];
|
||||
|
||||
overlay_context.outline(targets.iter(), viewport, None);
|
||||
}
|
||||
@@ -463,9 +457,7 @@ pub fn polygon_outline(layer: Option<LayerNodeIdentifier>, document: &DocumentMe
|
||||
let viewport = document.metadata().transform_to_viewport(layer);
|
||||
|
||||
let points = sides as u64;
|
||||
let radius: f64 = radius * 2.;
|
||||
|
||||
let targets: Vec<ClickTargetType> = vec![ClickTargetType::Path(Subpath::<PointId>::new_regular_polygon(DVec2::splat(-radius), points, radius).to_bezpath())];
|
||||
let targets: Vec<ClickTargetType> = vec![ClickTargetType::Path(regular_polygon_bezpath(DVec2::ZERO, points, radius))];
|
||||
|
||||
overlay_context.outline(targets.iter(), viewport, None);
|
||||
}
|
||||
@@ -478,8 +470,8 @@ pub fn arc_outline(layer: Option<LayerNodeIdentifier>, document: &DocumentMessag
|
||||
return;
|
||||
};
|
||||
|
||||
let arc = Subpath::<PointId>::new_arc(radius, start_angle / 360. * std::f64::consts::TAU, sweep_angle / 360. * std::f64::consts::TAU, arc_type);
|
||||
let targets: Vec<ClickTargetType> = vec![ClickTargetType::Path(arc.to_bezpath())];
|
||||
let arc = arc_bezpath(radius, start_angle / 360. * std::f64::consts::TAU, sweep_angle / 360. * std::f64::consts::TAU, arc_type);
|
||||
let targets: Vec<ClickTargetType> = vec![ClickTargetType::Path(arc)];
|
||||
let viewport = document.metadata().transform_to_viewport(layer);
|
||||
|
||||
overlay_context.outline(targets.iter(), viewport, None);
|
||||
|
||||
@@ -14,7 +14,7 @@ use crate::messages::tool::tool_messages::tool_prelude::*;
|
||||
use glam::DAffine2;
|
||||
use graph_craft::document::NodeInput;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graphene_std::subpath::{calculate_growth_factor, spiral_point};
|
||||
use graphene_std::vector::algorithms::shapes::{calculate_growth_factor, spiral_point};
|
||||
use graphene_std::vector::misc::SpiralType;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
|
||||
@@ -6,13 +6,11 @@ use crate::messages::prelude::*;
|
||||
use glam::{DAffine2, DVec2, FloatExt};
|
||||
use graphene_std::math::math_ext::QuadExt;
|
||||
use graphene_std::renderer::Quad;
|
||||
use graphene_std::subpath::pathseg_points;
|
||||
use graphene_std::subpath::{Identifier, ManipulatorGroup, Subpath};
|
||||
use graphene_std::vector::PointId;
|
||||
use graphene_std::vector::algorithms::bezpath_algorithms::{pathseg_normals_to_point, pathseg_tangents_to_point};
|
||||
use graphene_std::vector::algorithms::intersection::filtered_segment_intersections;
|
||||
use graphene_std::vector::misc::dvec2_to_point;
|
||||
use graphene_std::vector::misc::point_to_dvec2;
|
||||
use graphene_std::vector::algorithms::shapes::{ellipse_bezpath, line_bezpath};
|
||||
use graphene_std::vector::misc::{ManipulatorGroup, bezpath_from_manipulator_groups, dvec2_to_point, pathseg_points, point_to_dvec2};
|
||||
use kurbo::{Affine, BezPath, ParamCurve, PathEl, PathSeg};
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
@@ -47,7 +45,7 @@ impl LayerSnapper {
|
||||
self.paths_to_snap.push(SnapCandidatePath {
|
||||
document_curve,
|
||||
layer,
|
||||
start: PointId::new(),
|
||||
start: PointId::generate(),
|
||||
target,
|
||||
bounds: Some(bounds),
|
||||
});
|
||||
@@ -82,7 +80,7 @@ impl LayerSnapper {
|
||||
self.paths_to_snap.push(SnapCandidatePath {
|
||||
document_curve: Affine::new(transform.to_cols_array()) * curve,
|
||||
layer,
|
||||
start: PointId::new(),
|
||||
start: PointId::generate(),
|
||||
target: SnapTarget::Path(PathSnapTarget::AlongPath),
|
||||
bounds: None,
|
||||
});
|
||||
@@ -95,10 +93,11 @@ impl LayerSnapper {
|
||||
if let Some(vector) = document.network_interface.upstream_path_node_vector(layer) {
|
||||
let path_aware_transform = document.metadata().transform_to_document_if_feeds(layer, &document.network_interface);
|
||||
if path_aware_transform.is_finite() {
|
||||
for subpath in vector.stroke_bezier_paths() {
|
||||
for (start_index, curve) in subpath.iter().enumerate() {
|
||||
let start = subpath.manipulator_groups()[start_index].id;
|
||||
let end = subpath.manipulator_groups()[(start_index + 1) % subpath.len()].id;
|
||||
for (groups, closed) in vector.stroke_manipulator_groups() {
|
||||
let bezpath = bezpath_from_manipulator_groups(&groups, closed);
|
||||
for (start_index, curve) in bezpath.segments().enumerate() {
|
||||
let start = groups[start_index].id;
|
||||
let end = groups[(start_index + 1) % groups.len()].id;
|
||||
if snap_data.ignore_manipulator(layer, start) || snap_data.ignore_manipulator(layer, end) {
|
||||
continue;
|
||||
}
|
||||
@@ -163,17 +162,17 @@ impl LayerSnapper {
|
||||
|
||||
let tolerance = snap_tolerance(document);
|
||||
let constraint_path = if let SnapConstraint::Circle { center, radius } = constraint {
|
||||
Subpath::new_ellipse(center - DVec2::splat(radius), center + DVec2::splat(radius))
|
||||
ellipse_bezpath(center - DVec2::splat(radius), center + DVec2::splat(radius))
|
||||
} else {
|
||||
let constrained_point = constraint.projection(point.document_point);
|
||||
let direction = constraint.direction().normalize_or_zero();
|
||||
let start = constrained_point - tolerance * direction;
|
||||
let end = constrained_point + tolerance * direction;
|
||||
Subpath::<PointId>::new_line(start, end)
|
||||
line_bezpath(start, end)
|
||||
};
|
||||
|
||||
for path in &self.paths_to_snap {
|
||||
for constraint_path in constraint_path.iter() {
|
||||
for constraint_path in constraint_path.segments() {
|
||||
for time in filtered_segment_intersections(path.document_curve, constraint_path, None, None) {
|
||||
let snapped_point_document = point_to_dvec2(path.document_curve.eval(time));
|
||||
|
||||
@@ -555,13 +554,14 @@ fn handle_not_under(to_document: DAffine2) -> impl Fn(&DVec2) -> bool {
|
||||
move |&offset: &DVec2| to_document.transform_vector2(offset).length_squared() >= HIDE_HANDLE_DISTANCE * HIDE_HANDLE_DISTANCE
|
||||
}
|
||||
|
||||
fn subpath_anchor_snap_points(layer: LayerNodeIdentifier, subpath: &Subpath<PointId>, snap_data: &SnapData, points: &mut Vec<SnapCandidatePoint>, to_document: DAffine2) {
|
||||
fn manipulator_groups_anchor_snap_points(layer: LayerNodeIdentifier, groups: &[ManipulatorGroup], closed: bool, snap_data: &SnapData, points: &mut Vec<SnapCandidatePoint>, to_document: DAffine2) {
|
||||
let document = snap_data.document;
|
||||
let bezpath = bezpath_from_manipulator_groups(groups, closed);
|
||||
|
||||
// Midpoints of linear segments
|
||||
if document.snapping_state.target_enabled(SnapTarget::Path(PathSnapTarget::LineMidpoint)) {
|
||||
for (index, curve) in subpath.iter().enumerate() {
|
||||
if snap_data.ignore_manipulator(layer, subpath.manipulator_groups()[index].id) || snap_data.ignore_manipulator(layer, subpath.manipulator_groups()[(index + 1) % subpath.len()].id) {
|
||||
for (index, curve) in bezpath.segments().enumerate() {
|
||||
if snap_data.ignore_manipulator(layer, groups[index].id) || snap_data.ignore_manipulator(layer, groups[(index + 1) % groups.len()].id) {
|
||||
continue;
|
||||
}
|
||||
if points.len() >= crate::consts::MAX_LAYER_SNAP_POINTS {
|
||||
@@ -583,7 +583,7 @@ fn subpath_anchor_snap_points(layer: LayerNodeIdentifier, subpath: &Subpath<Poin
|
||||
}
|
||||
|
||||
// Anchors
|
||||
for (index, manipulators) in subpath.manipulator_groups().iter().enumerate() {
|
||||
for (index, manipulators) in groups.iter().enumerate() {
|
||||
if snap_data.ignore_manipulator(layer, manipulators.id) {
|
||||
continue;
|
||||
}
|
||||
@@ -592,7 +592,7 @@ fn subpath_anchor_snap_points(layer: LayerNodeIdentifier, subpath: &Subpath<Poin
|
||||
return;
|
||||
}
|
||||
|
||||
let colinear = are_manipulator_handles_colinear(manipulators, to_document, subpath, index);
|
||||
let colinear = are_manipulator_handles_colinear(manipulators, to_document, closed, index, groups.len());
|
||||
|
||||
// Colinear handles
|
||||
if colinear && document.snapping_state.target_enabled(SnapTarget::Path(PathSnapTarget::AnchorPointWithColinearHandles)) {
|
||||
@@ -705,11 +705,11 @@ fn bezpath_anchor_snap_points(layer: LayerNodeIdentifier, bezpath: &BezPath, sna
|
||||
}
|
||||
}
|
||||
|
||||
pub fn are_manipulator_handles_colinear(manipulators: &ManipulatorGroup<PointId>, to_document: DAffine2, subpath: &Subpath<PointId>, index: usize) -> bool {
|
||||
pub fn are_manipulator_handles_colinear(manipulators: &ManipulatorGroup, to_document: DAffine2, closed: bool, index: usize, count: usize) -> bool {
|
||||
let anchor = manipulators.anchor;
|
||||
let handle_in = manipulators.in_handle.map(|handle| anchor - handle).filter(handle_not_under(to_document));
|
||||
let handle_out = manipulators.out_handle.map(|handle| handle - anchor).filter(handle_not_under(to_document));
|
||||
let anchor_is_endpoint = !subpath.closed() && (index == 0 || index == subpath.len() - 1);
|
||||
let anchor_is_endpoint = !closed && (index == 0 || index == count - 1);
|
||||
|
||||
// Unless this is an endpoint, check if both handles are colinear (within an angular epsilon)
|
||||
!anchor_is_endpoint && handle_in.is_some_and(|handle_in| handle_out.is_some_and(|handle_out| handle_in.angle_to(handle_out) < 1e-5))
|
||||
@@ -743,8 +743,8 @@ pub fn get_layer_snap_points(layer: LayerNodeIdentifier, snap_data: &SnapData, p
|
||||
// matching what the Path tool's overlay shows
|
||||
if let Some(vector) = document.network_interface.upstream_path_node_vector(layer) {
|
||||
let to_document = document.metadata().transform_to_document_if_feeds(layer, &document.network_interface);
|
||||
for subpath in vector.stroke_bezier_paths() {
|
||||
subpath_anchor_snap_points(layer, &subpath, snap_data, points, to_document);
|
||||
for (groups, closed) in vector.stroke_manipulator_groups() {
|
||||
manipulator_groups_anchor_snap_points(layer, &groups, closed, snap_data, points, to_document);
|
||||
}
|
||||
|
||||
if document.snapping_state.target_enabled(SnapTarget::Path(PathSnapTarget::AnchorPointWithFreeHandles)) {
|
||||
|
||||
@@ -25,11 +25,11 @@ use crate::messages::tool::common_functionality::snapping::{SnapCache, SnapCandi
|
||||
use crate::messages::tool::common_functionality::utility_functions::{calculate_segment_angle, find_two_param_best_approximate, make_path_editable_is_allowed};
|
||||
use graphene_std::Color;
|
||||
use graphene_std::renderer::Quad;
|
||||
use graphene_std::subpath::pathseg_points;
|
||||
use graphene_std::transform::ReferencePoint;
|
||||
use graphene_std::uuid::NodeId;
|
||||
use graphene_std::vector::algorithms::util::pathseg_tangent;
|
||||
use graphene_std::vector::click_target::ClickTargetType;
|
||||
use graphene_std::vector::misc::pathseg_points;
|
||||
use graphene_std::vector::misc::{HandleId, ManipulatorPointId, dvec2_to_point, point_to_dvec2, segment_to_handles};
|
||||
use graphene_std::vector::{HandleExt, NoHashBuilder, PointId, SegmentId, Vector, VectorModificationType};
|
||||
use kurbo::{DEFAULT_ACCURACY, ParamCurve, ParamCurveNearest, PathSeg, Rect};
|
||||
|
||||
@@ -18,7 +18,7 @@ use crate::messages::tool::common_functionality::stroke_options::{StrokeOptionsU
|
||||
use crate::messages::tool::common_functionality::utility_functions::{calculate_segment_angle, closest_point, should_extend};
|
||||
use graph_craft::document::NodeId;
|
||||
use graphene_std::Color;
|
||||
use graphene_std::subpath::pathseg_points;
|
||||
use graphene_std::vector::misc::pathseg_points;
|
||||
use graphene_std::vector::misc::{HandleId, ManipulatorPointId, dvec2_to_point};
|
||||
use graphene_std::vector::style::FillChoice;
|
||||
use graphene_std::vector::{NoHashBuilder, PointId, SegmentId, StrokeId, Vector, VectorModificationType};
|
||||
@@ -1866,8 +1866,8 @@ impl Fsm for PenToolFsmState {
|
||||
.find(|(id, _, _, _)| id == &edge.id)
|
||||
.map(|(_, start, end, bezier)| if start == edge.start { (bezier, start, end) } else { (bezier.reversed(), end, start) })
|
||||
});
|
||||
if let Some(subpath) = vector.subpath_from_segments_ignore_discontinuities(segments) {
|
||||
fill_region.extend(subpath.to_bezpath().elements().iter().copied());
|
||||
if let Some(bezpath) = vector.bezpath_from_segments_ignore_discontinuities(segments) {
|
||||
fill_region.extend(bezpath.elements().iter().copied());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,8 +28,8 @@ use graph_craft::document::NodeId;
|
||||
use graphene_std::Color;
|
||||
use graphene_std::renderer::Quad;
|
||||
use graphene_std::renderer::Rect;
|
||||
use graphene_std::subpath::Subpath;
|
||||
use graphene_std::transform::ReferencePoint;
|
||||
use graphene_std::vector::algorithms::shapes::polyline_bezpath;
|
||||
use graphene_std::vector::misc::BooleanOperation;
|
||||
use graphene_std::vector::style::FillChoice;
|
||||
use std::fmt;
|
||||
@@ -575,7 +575,7 @@ impl SelectToolData {
|
||||
if self.lasso_polygon.len() < 2 {
|
||||
return Vec::new();
|
||||
}
|
||||
let polygon = Subpath::from_anchors(self.lasso_polygon.clone(), true);
|
||||
let polygon = polyline_bezpath(self.lasso_polygon.iter().copied(), true);
|
||||
document.intersect_polygon_no_artboards(polygon, viewport).collect()
|
||||
}
|
||||
|
||||
@@ -583,7 +583,7 @@ impl SelectToolData {
|
||||
if self.lasso_polygon.len() < 2 {
|
||||
return false;
|
||||
}
|
||||
let polygon = Subpath::from_anchors(self.lasso_polygon.clone(), true);
|
||||
let polygon = polyline_bezpath(self.lasso_polygon.iter().copied(), true);
|
||||
document.is_layer_fully_inside_polygon(layer, viewport, polygon)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user