mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-04 05:08:13 +08:00
Let vector modifier nodes operate on graphic content by recursing into groups
This commit is contained in:
@@ -17,7 +17,7 @@ use core_types::transform::Transform;
|
|||||||
use core_types::uuid::NodeId;
|
use core_types::uuid::NodeId;
|
||||||
use core_types::{ATTR_BLEND_MODE, ATTR_CLIPPING_MASK, ATTR_EDITOR_LAYER_PATH, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_TRANSFORM, CacheHash, Color, Ctx, DeriveCtx, ExtractIndex, InjectIndex};
|
use core_types::{ATTR_BLEND_MODE, ATTR_CLIPPING_MASK, ATTR_EDITOR_LAYER_PATH, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_TRANSFORM, CacheHash, Color, Ctx, DeriveCtx, ExtractIndex, InjectIndex};
|
||||||
use glam::{DAffine2, DMat2, DVec2};
|
use glam::{DAffine2, DMat2, DVec2};
|
||||||
use graphic_types::graphic::{bake_paint_transforms, has_paint, is_paint_present, set_paint_attribute_at};
|
use graphic_types::graphic::{MapVectorContent, bake_paint_transforms, has_paint, is_paint_present, set_paint_attribute_at};
|
||||||
use graphic_types::markers::{EditorMergedLayers, Fill, Stroke as StrokeAttr};
|
use graphic_types::markers::{EditorMergedLayers, Fill, Stroke as StrokeAttr};
|
||||||
use graphic_types::raster_types::{CPU, GPU, Raster};
|
use graphic_types::raster_types::{CPU, GPU, Raster};
|
||||||
use graphic_types::{ATTR_EDITOR_MERGED_LAYERS, ATTR_FILL, ATTR_STROKE, Graphic, IntoGraphicList};
|
use graphic_types::{ATTR_EDITOR_MERGED_LAYERS, ATTR_FILL, ATTR_STROKE, Graphic, IntoGraphicList};
|
||||||
@@ -595,10 +595,20 @@ fn copy_to_points_extent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Runs a per-vector modifier over the content, reaching every vector leaf a graphic holds.
|
||||||
|
fn map_vectors<'a, V: MapVectorContent>(
|
||||||
|
arena: &'a core_types::arena::Arena,
|
||||||
|
content: V,
|
||||||
|
transform: DAffine2,
|
||||||
|
mut map: impl FnMut(Vector, DAffine2) -> (Vector, DAffine2),
|
||||||
|
) -> Result<(V::Live<'a>, DAffine2), Interrupt> {
|
||||||
|
content.map_vector_content(arena, transform, &mut map).ok_or_else(|| GraphError::new("the arena is exhausted").into())
|
||||||
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
|
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
|
||||||
fn round_corners(
|
fn round_corners<'e, V: MapVectorContent + Clone + Send + Sync + CacheHash + 'static>(
|
||||||
_: impl Ctx,
|
ctx: impl Ctx + ExtractArena<'e>,
|
||||||
(source, transform): (Vector, Attr<TransformAttr>),
|
#[implementations(Graphic, Vector)] (source, transform): (V, Attr<TransformAttr>),
|
||||||
#[hard(0..)]
|
#[hard(0..)]
|
||||||
#[default(10.)]
|
#[default(10.)]
|
||||||
radius: PixelLength,
|
radius: PixelLength,
|
||||||
@@ -611,8 +621,8 @@ fn round_corners(
|
|||||||
#[hard(0..180)]
|
#[hard(0..180)]
|
||||||
#[default(5.)]
|
#[default(5.)]
|
||||||
min_angle_threshold: Angle,
|
min_angle_threshold: Angle,
|
||||||
) -> (Vector, Attr<TransformAttr>) {
|
) -> Result<(V::Live<'e>, Attr<TransformAttr>), Interrupt> {
|
||||||
let source_transform: DAffine2 = *transform;
|
let (source, transform) = map_vectors(ctx.arena(), source, *transform, |source, source_transform| {
|
||||||
let source_transform_inverse = source_transform.inverse();
|
let source_transform_inverse = source_transform.inverse();
|
||||||
|
|
||||||
// Flip the roundness to help with user intuition
|
// Flip the roundness to help with user intuition
|
||||||
@@ -698,23 +708,31 @@ fn round_corners(
|
|||||||
result.append_bezpath(rounded_subpath);
|
result.append_bezpath(rounded_subpath);
|
||||||
}
|
}
|
||||||
|
|
||||||
(result, Attr(source_transform))
|
(result, source_transform)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok((source, Attr(transform)))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(name("Merge by Distance"), category("Vector: Modifier"), path(core_types::vector))]
|
#[node_macro::node(name("Merge by Distance"), category("Vector: Modifier"), path(core_types::vector))]
|
||||||
pub fn merge_by_distance(
|
pub fn merge_by_distance<'e, V: MapVectorContent + Clone + Send + Sync + CacheHash + 'static>(
|
||||||
_: impl Ctx,
|
ctx: impl Ctx + ExtractArena<'e>,
|
||||||
(mut content, transform): (Vector, Attr<TransformAttr>),
|
#[implementations(Graphic, Vector)] (content, transform): (V, Attr<TransformAttr>),
|
||||||
#[default(0.1)]
|
#[default(0.1)]
|
||||||
#[hard(0.0001..)]
|
#[hard(0.0001..)]
|
||||||
distance: PixelLength,
|
distance: PixelLength,
|
||||||
algorithm: MergeByDistanceAlgorithm,
|
algorithm: MergeByDistanceAlgorithm,
|
||||||
) -> (Vector, Attr<TransformAttr>) {
|
) -> Result<(V::Live<'e>, Attr<TransformAttr>), Interrupt> {
|
||||||
|
let (content, transform) = map_vectors(ctx.arena(), content, *transform, |mut content, transform| {
|
||||||
match algorithm {
|
match algorithm {
|
||||||
MergeByDistanceAlgorithm::Spatial => content.merge_by_distance_spatial(*transform, distance),
|
MergeByDistanceAlgorithm::Spatial => content.merge_by_distance_spatial(transform, distance),
|
||||||
MergeByDistanceAlgorithm::Topological => content.merge_by_distance_topological(distance),
|
MergeByDistanceAlgorithm::Topological => content.merge_by_distance_topological(distance),
|
||||||
}
|
}
|
||||||
(content, Attr(*transform))
|
|
||||||
|
(content, transform)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok((content, Attr(transform)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod extrude_algorithms {
|
pub mod extrude_algorithms {
|
||||||
@@ -915,19 +933,33 @@ pub mod extrude_algorithms {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
|
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
|
||||||
fn extrude(_: impl Ctx, mut source: Vector, direction: DVec2, joining_algorithm: ExtrudeJoiningAlgorithm) -> Vector {
|
fn extrude<'e, V: MapVectorContent + Clone + Send + Sync + CacheHash + 'static>(
|
||||||
|
ctx: impl Ctx + ExtractArena<'e>,
|
||||||
|
#[implementations(Graphic, Vector)] source: V,
|
||||||
|
direction: DVec2,
|
||||||
|
joining_algorithm: ExtrudeJoiningAlgorithm,
|
||||||
|
) -> Result<V::Live<'e>, Interrupt> {
|
||||||
|
let (source, _) = map_vectors(ctx.arena(), source, DAffine2::IDENTITY, |mut source, transform| {
|
||||||
extrude_algorithms::extrude(&mut source, direction, joining_algorithm);
|
extrude_algorithms::extrude(&mut source, direction, joining_algorithm);
|
||||||
source
|
|
||||||
|
(source, transform)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(source)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
|
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
|
||||||
fn box_warp(_: impl Ctx, (vector, transform): (Vector, Attr<TransformAttr>), #[expose] rectangle: IList<Vector>) -> (Vector, Attr<TransformAttr>) {
|
fn box_warp<'e, V: MapVectorContent + Clone + Send + Sync + CacheHash + 'static>(
|
||||||
|
ctx: impl Ctx + ExtractArena<'e>,
|
||||||
|
#[implementations(Graphic, Vector)] (content, lane_transform): (V, Attr<TransformAttr>),
|
||||||
|
#[expose] rectangle: IList<Vector>,
|
||||||
|
) -> Result<(V::Live<'e>, Attr<TransformAttr>), Interrupt> {
|
||||||
|
let (content, lane_transform) = map_vectors(ctx.arena(), content, *lane_transform, |vector, transform| {
|
||||||
if rectangle.is_empty() {
|
if rectangle.is_empty() {
|
||||||
return (vector, Attr(*transform));
|
return (vector, transform);
|
||||||
}
|
}
|
||||||
let target = rectangle.element_ref(0);
|
let target = rectangle.element_ref(0);
|
||||||
let target_transform: DAffine2 = rectangle.lane(0).attr::<TransformAttr>();
|
let target_transform: DAffine2 = rectangle.lane(0).attr::<TransformAttr>();
|
||||||
let transform: DAffine2 = *transform;
|
|
||||||
|
|
||||||
// Get the bounding box of the source vector geometry
|
// Get the bounding box of the source vector geometry
|
||||||
let source_bbox = vector.bounding_box_with_transform(transform).unwrap_or([DVec2::ZERO, DVec2::ONE]);
|
let source_bbox = vector.bounding_box_with_transform(transform).unwrap_or([DVec2::ZERO, DVec2::ONE]);
|
||||||
@@ -985,7 +1017,10 @@ fn box_warp(_: impl Ctx, (vector, transform): (Vector, Attr<TransformAttr>), #[e
|
|||||||
result.set_stroke_transform(DAffine2::IDENTITY);
|
result.set_stroke_transform(DAffine2::IDENTITY);
|
||||||
|
|
||||||
// Reset the transform since we've applied it directly to the points
|
// Reset the transform since we've applied it directly to the points
|
||||||
(result, Attr(DAffine2::IDENTITY))
|
(result, DAffine2::IDENTITY)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok((content, Attr(lane_transform)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interpolate within a quadrilateral using normalized coordinates (0-1)
|
// Interpolate within a quadrilateral using normalized coordinates (0-1)
|
||||||
@@ -1115,9 +1150,9 @@ fn pack_strips_extent<T>(elements: ListIn<'_, T>, _separation: ValueIn<'_, f64>,
|
|||||||
|
|
||||||
/// Automatically constructs tangents (Bézier handles) for anchor points in a vector path.
|
/// Automatically constructs tangents (Bézier handles) for anchor points in a vector path.
|
||||||
#[node_macro::node(category("Vector: Modifier"), name("Auto-Tangents"), path(core_types::vector))]
|
#[node_macro::node(category("Vector: Modifier"), name("Auto-Tangents"), path(core_types::vector))]
|
||||||
fn auto_tangents(
|
fn auto_tangents<'e, V: MapVectorContent + Clone + Send + Sync + CacheHash + 'static>(
|
||||||
_: impl Ctx,
|
ctx: impl Ctx + ExtractArena<'e>,
|
||||||
(source, lane_transform): (Vector, Attr<TransformAttr>),
|
#[implementations(Graphic, Vector)] (source, lane_transform): (V, Attr<TransformAttr>),
|
||||||
/// The amount of spread for the auto-tangents, from 0 (sharp corner) to 1 (full spread).
|
/// The amount of spread for the auto-tangents, from 0 (sharp corner) to 1 (full spread).
|
||||||
#[default(0.5)]
|
#[default(0.5)]
|
||||||
#[range]
|
#[range]
|
||||||
@@ -1126,9 +1161,8 @@ fn auto_tangents(
|
|||||||
/// If active, existing non-zero handles won't be affected.
|
/// If active, existing non-zero handles won't be affected.
|
||||||
#[default(true)]
|
#[default(true)]
|
||||||
preserve_existing: bool,
|
preserve_existing: bool,
|
||||||
) -> (Vector, Attr<TransformAttr>) {
|
) -> Result<(V::Live<'e>, Attr<TransformAttr>), Interrupt> {
|
||||||
let transform: DAffine2 = *lane_transform;
|
let (source, transform) = map_vectors(ctx.arena(), source, *lane_transform, |source, transform| {
|
||||||
|
|
||||||
let mut result = Vector {
|
let mut result = Vector {
|
||||||
stroke: source.stroke.clone(),
|
stroke: source.stroke.clone(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@@ -1259,11 +1293,18 @@ fn auto_tangents(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(result, Attr(transform))
|
(result, transform)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok((source, Attr(transform)))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
|
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
|
||||||
fn bounding_box(_: impl Ctx, vector: Vector) -> Vector {
|
fn bounding_box<'e, V: MapVectorContent + Clone + Send + Sync + CacheHash + 'static>(
|
||||||
|
ctx: impl Ctx + ExtractArena<'e>,
|
||||||
|
#[implementations(Graphic, Vector)] content: V,
|
||||||
|
) -> Result<V::Live<'e>, Interrupt> {
|
||||||
|
let (content, _) = map_vectors(ctx.arena(), content, DAffine2::IDENTITY, |vector, transform| {
|
||||||
let mut result = vector
|
let mut result = vector
|
||||||
.bounding_box_rect()
|
.bounding_box_rect()
|
||||||
.map(|bbox| {
|
.map(|bbox| {
|
||||||
@@ -1276,7 +1317,10 @@ fn bounding_box(_: impl Ctx, vector: Vector) -> Vector {
|
|||||||
result.stroke = vector.stroke.clone();
|
result.stroke = vector.stroke.clone();
|
||||||
result.set_stroke_transform(DAffine2::IDENTITY);
|
result.set_stroke_transform(DAffine2::IDENTITY);
|
||||||
|
|
||||||
result
|
(result, transform)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Vector: Measure"), path(core_types::vector))]
|
#[node_macro::node(category("Vector: Measure"), path(core_types::vector))]
|
||||||
@@ -1296,7 +1340,12 @@ fn as_vector<T: Into<Vector>>(_: impl Ctx, #[implementations(Vector, DVec2)] val
|
|||||||
|
|
||||||
/// Creates a polyline from a series of vector points, replacing any existing segments and regions that may already exist.
|
/// Creates a polyline from a series of vector points, replacing any existing segments and regions that may already exist.
|
||||||
#[node_macro::node(category("Vector"), name("Points to Polyline"), path(core_types::vector))]
|
#[node_macro::node(category("Vector"), name("Points to Polyline"), path(core_types::vector))]
|
||||||
fn points_to_polyline(_: impl Ctx, mut points: Vector, #[default(true)] closed: bool) -> Vector {
|
fn points_to_polyline<'e, V: MapVectorContent + Clone + Send + Sync + CacheHash + 'static>(
|
||||||
|
ctx: impl Ctx + ExtractArena<'e>,
|
||||||
|
#[implementations(Graphic, Vector)] content: V,
|
||||||
|
#[default(true)] closed: bool,
|
||||||
|
) -> Result<V::Live<'e>, Interrupt> {
|
||||||
|
let (content, _) = map_vectors(ctx.arena(), content, DAffine2::IDENTITY, |mut points, transform| {
|
||||||
let mut segment_domain = SegmentDomain::new();
|
let mut segment_domain = SegmentDomain::new();
|
||||||
let mut next_id = SegmentId::ZERO;
|
let mut next_id = SegmentId::ZERO;
|
||||||
|
|
||||||
@@ -1318,47 +1367,68 @@ fn points_to_polyline(_: impl Ctx, mut points: Vector, #[default(true)] closed:
|
|||||||
|
|
||||||
points.segment_domain = segment_domain;
|
points.segment_domain = segment_domain;
|
||||||
|
|
||||||
points
|
(points, transform)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evens out the distances between points by applying Lloyd's relaxation, moving every interior point toward the center of its Voronoi cell.
|
/// Evens out the distances between points by applying Lloyd's relaxation, moving every interior point toward the center of its Voronoi cell.
|
||||||
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
|
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
|
||||||
fn relax_points(
|
fn relax_points<'e, V: MapVectorContent + Clone + Send + Sync + CacheHash + 'static>(
|
||||||
_: impl Ctx,
|
ctx: impl Ctx + ExtractArena<'e>,
|
||||||
/// A vector path or point cloud to relax.
|
/// A vector path or point cloud to relax.
|
||||||
mut source: Vector,
|
#[implementations(Graphic, Vector)]
|
||||||
|
content: V,
|
||||||
/// The number of relaxation steps to apply. A fractional value runs the whole steps and then blends partway toward one more step, so the amount of relaxation can be animated smoothly.
|
/// The number of relaxation steps to apply. A fractional value runs the whole steps and then blends partway toward one more step, so the amount of relaxation can be animated smoothly.
|
||||||
#[default(1.)]
|
#[default(1.)]
|
||||||
#[hard(0..1000)]
|
#[hard(0..1000)]
|
||||||
iterations: f64,
|
iterations: f64,
|
||||||
) -> Vector {
|
) -> Result<V::Live<'e>, Interrupt> {
|
||||||
|
let (content, _) = map_vectors(ctx.arena(), content, DAffine2::IDENTITY, |mut source, transform| {
|
||||||
let relaxed = crate::voronoi::relax_sites(source.point_domain.positions(), iterations);
|
let relaxed = crate::voronoi::relax_sites(source.point_domain.positions(), iterations);
|
||||||
for ((_, position), new_position) in source.point_domain.positions_mut().zip(relaxed) {
|
for ((_, position), new_position) in source.point_domain.positions_mut().zip(relaxed) {
|
||||||
*position = new_position;
|
*position = new_position;
|
||||||
}
|
}
|
||||||
|
|
||||||
source
|
(source, transform)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Builds a Voronoi diagram from the anchor points. Each point claims the region of space closest to it, and those regions tessellate the plane. Cells around the outside are clipped to the convex hull of the points so the diagram stays finite.
|
/// Builds a Voronoi diagram from the anchor points. Each point claims the region of space closest to it, and those regions tessellate the plane. Cells around the outside are clipped to the convex hull of the points so the diagram stays finite.
|
||||||
///
|
///
|
||||||
/// When Connect Cells is off, every cell becomes its own closed, fillable subpath. When on, the cells share their common points and segments, forming a single connected mesh with no fillable regions.
|
/// When Connect Cells is off, every cell becomes its own closed, fillable subpath. When on, the cells share their common points and segments, forming a single connected mesh with no fillable regions.
|
||||||
#[node_macro::node(category("Vector"), path(core_types::vector))]
|
#[node_macro::node(category("Vector"), path(core_types::vector))]
|
||||||
fn voronoi_cells(_: impl Ctx, mut source: Vector, connect_cells: bool) -> Vector {
|
fn voronoi_cells<'e, V: MapVectorContent + Clone + Send + Sync + CacheHash + 'static>(
|
||||||
|
ctx: impl Ctx + ExtractArena<'e>,
|
||||||
|
#[implementations(Graphic, Vector)] content: V,
|
||||||
|
connect_cells: bool,
|
||||||
|
) -> Result<V::Live<'e>, Interrupt> {
|
||||||
|
let (content, _) = map_vectors(ctx.arena(), content, DAffine2::IDENTITY, |mut source, transform| {
|
||||||
let sites = source.point_domain.positions().to_vec();
|
let sites = source.point_domain.positions().to_vec();
|
||||||
let cells = crate::voronoi::voronoi_cells(&sites);
|
let cells = crate::voronoi::voronoi_cells(&sites);
|
||||||
if !cells.is_empty() {
|
if !cells.is_empty() {
|
||||||
replace_with_polygons(&mut source, cells, connect_cells);
|
replace_with_polygons(&mut source, cells, connect_cells);
|
||||||
}
|
}
|
||||||
|
|
||||||
source
|
(source, transform)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Builds a Delaunay triangulation connecting the anchor points. It is the geometric dual of the **Voronoi** node: a mesh of triangles in which no point lies inside any triangle's circumscribed circle.
|
/// Builds a Delaunay triangulation connecting the anchor points. It is the geometric dual of the **Voronoi** node: a mesh of triangles in which no point lies inside any triangle's circumscribed circle.
|
||||||
///
|
///
|
||||||
/// When Connect Cells is off, every triangle becomes its own closed, fillable subpath. When on, the triangles share their common points and segments, forming a single connected mesh with no fillable regions.
|
/// When Connect Cells is off, every triangle becomes its own closed, fillable subpath. When on, the triangles share their common points and segments, forming a single connected mesh with no fillable regions.
|
||||||
#[node_macro::node(category("Vector"), path(core_types::vector))]
|
#[node_macro::node(category("Vector"), path(core_types::vector))]
|
||||||
fn triangulate(_: impl Ctx, mut source: Vector, connect_cells: bool) -> Vector {
|
fn triangulate<'e, V: MapVectorContent + Clone + Send + Sync + CacheHash + 'static>(
|
||||||
|
ctx: impl Ctx + ExtractArena<'e>,
|
||||||
|
#[implementations(Graphic, Vector)] content: V,
|
||||||
|
connect_cells: bool,
|
||||||
|
) -> Result<V::Live<'e>, Interrupt> {
|
||||||
|
let (content, _) = map_vectors(ctx.arena(), content, DAffine2::IDENTITY, |mut source, transform| {
|
||||||
let sites = source.point_domain.positions().to_vec();
|
let sites = source.point_domain.positions().to_vec();
|
||||||
let triangles = crate::voronoi::delaunay_triangles(&sites);
|
let triangles = crate::voronoi::delaunay_triangles(&sites);
|
||||||
if !triangles.is_empty() {
|
if !triangles.is_empty() {
|
||||||
@@ -1368,7 +1438,10 @@ fn triangulate(_: impl Ctx, mut source: Vector, connect_cells: bool) -> Vector {
|
|||||||
replace_with_polygons(&mut source, polygons, connect_cells);
|
replace_with_polygons(&mut source, polygons, connect_cells);
|
||||||
}
|
}
|
||||||
|
|
||||||
source
|
(source, transform)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Replaces a vector's geometry (points, segments, and regions) with the given closed polygons, preserving its style.
|
/// Replaces a vector's geometry (points, segments, and regions) with the given closed polygons, preserving its style.
|
||||||
@@ -1472,8 +1545,14 @@ fn mesh_weld_tolerance(polygons: &[Vec<DVec2>]) -> f64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector), properties("offset_path_properties"))]
|
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector), properties("offset_path_properties"))]
|
||||||
fn offset_path(_: impl Ctx, (vector, lane_transform): (Vector, Attr<TransformAttr>), distance: f64, join: StrokeJoin, #[default(4.)] miter_limit: f64) -> (Vector, Attr<TransformAttr>) {
|
fn offset_path<'e, V: MapVectorContent + Clone + Send + Sync + CacheHash + 'static>(
|
||||||
let transform_attribute: DAffine2 = *lane_transform;
|
ctx: impl Ctx + ExtractArena<'e>,
|
||||||
|
#[implementations(Graphic, Vector)] (content, lane_transform): (V, Attr<TransformAttr>),
|
||||||
|
distance: f64,
|
||||||
|
join: StrokeJoin,
|
||||||
|
#[default(4.)] miter_limit: f64,
|
||||||
|
) -> Result<(V::Live<'e>, Attr<TransformAttr>), Interrupt> {
|
||||||
|
let (content, lane_transform) = map_vectors(ctx.arena(), content, *lane_transform, |vector, transform_attribute| {
|
||||||
let transform = Affine::new(transform_attribute.to_cols_array());
|
let transform = Affine::new(transform_attribute.to_cols_array());
|
||||||
|
|
||||||
let bezpaths = vector.stroke_bezpath_iter();
|
let bezpaths = vector.stroke_bezpath_iter();
|
||||||
@@ -1505,7 +1584,10 @@ fn offset_path(_: impl Ctx, (vector, lane_transform): (Vector, Attr<TransformAtt
|
|||||||
result.append_bezpath(bezpath_out);
|
result.append_bezpath(bezpath_out);
|
||||||
}
|
}
|
||||||
|
|
||||||
(result, Attr(transform_attribute))
|
(result, transform_attribute)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok((content, Attr(lane_transform)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn solidify_rows(flattened: List<Vector>) -> List<Vector> {
|
fn solidify_rows(flattened: List<Vector>) -> List<Vector> {
|
||||||
@@ -2125,9 +2207,9 @@ pub use _combine_paths_vector_mod::combine_paths_vector_entries;
|
|||||||
|
|
||||||
/// Convert vector geometry into a polyline composed of evenly spaced points.
|
/// Convert vector geometry into a polyline composed of evenly spaced points.
|
||||||
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector), properties("sample_polyline_properties"), memoize)]
|
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector), properties("sample_polyline_properties"), memoize)]
|
||||||
fn sample_polyline(
|
fn sample_polyline<'e, V: MapVectorContent + Clone + Send + Sync + CacheHash + 'static>(
|
||||||
_: impl Ctx,
|
ctx: impl Ctx + ExtractArena<'e>,
|
||||||
(element, transform): (Vector, Attr<TransformAttr>),
|
#[implementations(Graphic, Vector)] (content, lane_transform): (V, Attr<TransformAttr>),
|
||||||
spacing: PointSpacingType,
|
spacing: PointSpacingType,
|
||||||
#[default(100.)]
|
#[default(100.)]
|
||||||
#[hard(0..)]
|
#[hard(0..)]
|
||||||
@@ -2143,7 +2225,8 @@ fn sample_polyline(
|
|||||||
#[unit(" px")]
|
#[unit(" px")]
|
||||||
stop_offset: f64,
|
stop_offset: f64,
|
||||||
adaptive_spacing: bool,
|
adaptive_spacing: bool,
|
||||||
) -> (Vector, Attr<TransformAttr>) {
|
) -> Result<(V::Live<'e>, Attr<TransformAttr>), Interrupt> {
|
||||||
|
let (content, lane_transform) = map_vectors(ctx.arena(), content, *lane_transform, |element, transform| {
|
||||||
let pathseg_perimeter = |segment: PathSeg| {
|
let pathseg_perimeter = |segment: PathSeg| {
|
||||||
if is_linear(segment) {
|
if is_linear(segment) {
|
||||||
Line::new(segment.start(), segment.end()).perimeter(DEFAULT_ACCURACY)
|
Line::new(segment.start(), segment.end()).perimeter(DEFAULT_ACCURACY)
|
||||||
@@ -2161,7 +2244,7 @@ fn sample_polyline(
|
|||||||
stroke: std::mem::take(&mut element.stroke),
|
stroke: std::mem::take(&mut element.stroke),
|
||||||
};
|
};
|
||||||
// Transfer the stroke transform from the input vector content to the result.
|
// Transfer the stroke transform from the input vector content to the result.
|
||||||
result.set_stroke_transform(*transform);
|
result.set_stroke_transform(transform);
|
||||||
|
|
||||||
for local_bezpath in element.stroke_bezpath_iter() {
|
for local_bezpath in element.stroke_bezpath_iter() {
|
||||||
// Apply the transform to compute sample locations in world space (for correct distance-based spacing)
|
// Apply the transform to compute sample locations in world space (for correct distance-based spacing)
|
||||||
@@ -2200,27 +2283,31 @@ fn sample_polyline(
|
|||||||
result.append_bezpath(sample_bezpath);
|
result.append_bezpath(sample_bezpath);
|
||||||
}
|
}
|
||||||
|
|
||||||
(result, Attr(*transform))
|
(result, transform)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok((content, Attr(lane_transform)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Simplifies vector paths by reducing the number of curve segments while preserving the overall shape within the given tolerance.
|
/// Simplifies vector paths by reducing the number of curve segments while preserving the overall shape within the given tolerance.
|
||||||
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
|
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
|
||||||
fn simplify(
|
fn simplify<'e, V: MapVectorContent + Clone + Send + Sync + CacheHash + 'static>(
|
||||||
_: impl Ctx,
|
ctx: impl Ctx + ExtractArena<'e>,
|
||||||
/// The vector paths to simplify.
|
/// The vector paths to simplify.
|
||||||
(content, lane_transform): (Vector, Attr<TransformAttr>),
|
#[implementations(Graphic, Vector)]
|
||||||
|
(content, lane_transform): (V, Attr<TransformAttr>),
|
||||||
/// The maximum distance the simplified path may deviate from the original.
|
/// The maximum distance the simplified path may deviate from the original.
|
||||||
#[default(5.)]
|
#[default(5.)]
|
||||||
#[unit(" px")]
|
#[unit(" px")]
|
||||||
tolerance: Length,
|
tolerance: Length,
|
||||||
) -> (Vector, Attr<TransformAttr>) {
|
) -> Result<(V::Live<'e>, Attr<TransformAttr>), Interrupt> {
|
||||||
|
let (content, lane_transform) = map_vectors(ctx.arena(), content, *lane_transform, |content, transform_attribute| {
|
||||||
if tolerance <= 0. {
|
if tolerance <= 0. {
|
||||||
return (content, Attr(*lane_transform));
|
return (content, transform_attribute);
|
||||||
}
|
}
|
||||||
|
|
||||||
let options = SimplifyOptions::default();
|
let options = SimplifyOptions::default();
|
||||||
|
|
||||||
let transform_attribute: DAffine2 = *lane_transform;
|
|
||||||
let transform = Affine::new(transform_attribute.to_cols_array());
|
let transform = Affine::new(transform_attribute.to_cols_array());
|
||||||
let inverse_transform = transform.inverse();
|
let inverse_transform = transform.inverse();
|
||||||
|
|
||||||
@@ -2238,23 +2325,28 @@ fn simplify(
|
|||||||
result.append_bezpath(simplified);
|
result.append_bezpath(simplified);
|
||||||
}
|
}
|
||||||
|
|
||||||
(result, Attr(transform_attribute))
|
(result, transform_attribute)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok((content, Attr(lane_transform)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Decimates vector paths into polylines by sampling any curves into line segments, then removing points that don't significantly contribute to the shape using the Ramer-Douglas-Peucker algorithm.
|
/// Decimates vector paths into polylines by sampling any curves into line segments, then removing points that don't significantly contribute to the shape using the Ramer-Douglas-Peucker algorithm.
|
||||||
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
|
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
|
||||||
fn decimate(
|
fn decimate<'e, V: MapVectorContent + Clone + Send + Sync + CacheHash + 'static>(
|
||||||
_: impl Ctx,
|
ctx: impl Ctx + ExtractArena<'e>,
|
||||||
/// The vector paths to decimate.
|
/// The vector paths to decimate.
|
||||||
(content, lane_transform): (Vector, Attr<TransformAttr>),
|
#[implementations(Graphic, Vector)]
|
||||||
|
(content, lane_transform): (V, Attr<TransformAttr>),
|
||||||
/// The maximum distance a point can deviate from the simplified path before it is kept.
|
/// The maximum distance a point can deviate from the simplified path before it is kept.
|
||||||
#[default(5.)]
|
#[default(5.)]
|
||||||
#[unit(" px")]
|
#[unit(" px")]
|
||||||
tolerance: Length,
|
tolerance: Length,
|
||||||
) -> (Vector, Attr<TransformAttr>) {
|
) -> Result<(V::Live<'e>, Attr<TransformAttr>), Interrupt> {
|
||||||
|
let (content, lane_transform) = map_vectors(ctx.arena(), content, *lane_transform, |content, transform_attribute| {
|
||||||
// Tolerance of 0 means no simplification is possible, so return immediately
|
// Tolerance of 0 means no simplification is possible, so return immediately
|
||||||
if tolerance <= 0. {
|
if tolerance <= 0. {
|
||||||
return (content, Attr(*lane_transform));
|
return (content, transform_attribute);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Below this squared length, a line segment is treated as a degenerate point and the distance
|
// Below this squared length, a line segment is treated as a degenerate point and the distance
|
||||||
@@ -2310,7 +2402,6 @@ fn decimate(
|
|||||||
points.iter().enumerate().filter(|(i, _)| keep[*i]).map(|(_, p)| *p).collect()
|
points.iter().enumerate().filter(|(i, _)| keep[*i]).map(|(_, p)| *p).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
let transform_attribute: DAffine2 = *lane_transform;
|
|
||||||
let transform = Affine::new(transform_attribute.to_cols_array());
|
let transform = Affine::new(transform_attribute.to_cols_array());
|
||||||
let inverse_transform = transform.inverse();
|
let inverse_transform = transform.inverse();
|
||||||
|
|
||||||
@@ -2358,7 +2449,10 @@ fn decimate(
|
|||||||
result.append_bezpath(new_bezpath);
|
result.append_bezpath(new_bezpath);
|
||||||
}
|
}
|
||||||
|
|
||||||
(result, Attr(transform_attribute))
|
(result, transform_attribute)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok((content, Attr(lane_transform)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The materialized vector level as the owned rows the cross-lane cores walk,
|
/// The materialized vector level as the owned rows the cross-lane cores walk,
|
||||||
@@ -2454,7 +2548,11 @@ fn cut_path_extent(content: ListIn<'_, Vector>, _progression: ValueIn<'_, f64>,
|
|||||||
|
|
||||||
/// Cuts path segments into separate disconnected pieces where each is a distinct subpath.
|
/// Cuts path segments into separate disconnected pieces where each is a distinct subpath.
|
||||||
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
|
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
|
||||||
fn cut_segments(_: impl Ctx, mut content: Vector) -> Vector {
|
fn cut_segments<'e, V: MapVectorContent + Clone + Send + Sync + CacheHash + 'static>(
|
||||||
|
ctx: impl Ctx + ExtractArena<'e>,
|
||||||
|
#[implementations(Graphic, Vector)] source: V,
|
||||||
|
) -> Result<V::Live<'e>, Interrupt> {
|
||||||
|
let (source, _) = map_vectors(ctx.arena(), source, DAffine2::IDENTITY, |mut content, lane| {
|
||||||
// Make a copy of each segment's endpoints, then reassign each segment's endpoints to its own unique point copy
|
// Make a copy of each segment's endpoints, then reassign each segment's endpoints to its own unique point copy
|
||||||
{
|
{
|
||||||
let vector = &mut content;
|
let vector = &mut content;
|
||||||
@@ -2507,7 +2605,10 @@ fn cut_segments(_: impl Ctx, mut content: Vector) -> Vector {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
content
|
(content, lane)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(source)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determines the position of a point on the path, given by its progression from 0 to 1 along the path.
|
/// Determines the position of a point on the path, given by its progression from 0 to 1 along the path.
|
||||||
@@ -2600,9 +2701,9 @@ fn tangent_on_path(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector), memoize)]
|
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector), memoize)]
|
||||||
fn scatter_points(
|
fn scatter_points<'e, V: MapVectorContent + Clone + Send + Sync + CacheHash + 'static>(
|
||||||
_: impl Ctx,
|
ctx: impl Ctx + ExtractArena<'e>,
|
||||||
element: Vector,
|
#[implementations(Graphic, Vector)] content: V,
|
||||||
#[unit(" px")]
|
#[unit(" px")]
|
||||||
#[default(10.)]
|
#[default(10.)]
|
||||||
#[range]
|
#[range]
|
||||||
@@ -2610,7 +2711,8 @@ fn scatter_points(
|
|||||||
#[soft(1..100)]
|
#[soft(1..100)]
|
||||||
separation: f64,
|
separation: f64,
|
||||||
seed: SeedValue,
|
seed: SeedValue,
|
||||||
) -> Vector {
|
) -> Result<V::Live<'e>, Interrupt> {
|
||||||
|
let (content, _) = map_vectors(ctx.arena(), content, DAffine2::IDENTITY, |element, lane| {
|
||||||
let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into());
|
let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into());
|
||||||
|
|
||||||
let mut result = Vector::default();
|
let mut result = Vector::default();
|
||||||
@@ -2639,14 +2741,18 @@ fn scatter_points(
|
|||||||
result.stroke = element.stroke.clone();
|
result.stroke = element.stroke.clone();
|
||||||
result.set_stroke_transform(DAffine2::IDENTITY);
|
result.set_stroke_transform(DAffine2::IDENTITY);
|
||||||
|
|
||||||
result
|
(result, lane)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(name("Spline"), category("Vector: Modifier"), path(core_types::vector))]
|
#[node_macro::node(name("Spline"), category("Vector: Modifier"), path(core_types::vector))]
|
||||||
fn spline(_: impl Ctx, element: Vector) -> Vector {
|
fn spline<'e, V: MapVectorContent + Clone + Send + Sync + CacheHash + 'static>(ctx: impl Ctx + ExtractArena<'e>, #[implementations(Graphic, Vector)] content: V) -> Result<V::Live<'e>, Interrupt> {
|
||||||
|
let (content, _) = map_vectors(ctx.arena(), content, DAffine2::IDENTITY, |element, transform| {
|
||||||
// Exit early if there are no points to generate splines from.
|
// Exit early if there are no points to generate splines from.
|
||||||
if element.point_domain.positions().is_empty() {
|
if element.point_domain.positions().is_empty() {
|
||||||
return element;
|
return (element, transform);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut segment_domain = SegmentDomain::default();
|
let mut segment_domain = SegmentDomain::default();
|
||||||
@@ -2681,7 +2787,11 @@ fn spline(_: impl Ctx, element: Vector) -> Vector {
|
|||||||
|
|
||||||
let mut element = element;
|
let mut element = element;
|
||||||
element.segment_domain = segment_domain;
|
element.segment_domain = segment_domain;
|
||||||
element
|
|
||||||
|
(element, transform)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Computes the inverse of a transform's linear (matrix2) part, handling singular transforms
|
/// Computes the inverse of a transform's linear (matrix2) part, handling singular transforms
|
||||||
@@ -2739,10 +2849,11 @@ fn apply_point_deltas(element: &mut Vector, deltas: &[DVec2], transform: DAffine
|
|||||||
|
|
||||||
/// Perturbs the positions of anchor points in vector geometry by random amounts and directions.
|
/// Perturbs the positions of anchor points in vector geometry by random amounts and directions.
|
||||||
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
|
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
|
||||||
fn jitter_points(
|
fn jitter_points<'e, V: MapVectorContent + Clone + Send + Sync + CacheHash + 'static>(
|
||||||
_: impl Ctx,
|
ctx: impl Ctx + ExtractArena<'e>,
|
||||||
/// The vector geometry with points to be jittered.
|
/// The vector geometry with points to be jittered.
|
||||||
(element, transform): (Vector, Attr<TransformAttr>),
|
#[implementations(Graphic, Vector)]
|
||||||
|
(content, lane_transform): (V, Attr<TransformAttr>),
|
||||||
/// The maximum extent of the random distance each point can be offset.
|
/// The maximum extent of the random distance each point can be offset.
|
||||||
#[default(5.)]
|
#[default(5.)]
|
||||||
#[unit(" px")]
|
#[unit(" px")]
|
||||||
@@ -2752,7 +2863,8 @@ fn jitter_points(
|
|||||||
/// Whether to offset anchor points along their normal direction (perpendicular to the path) or in a random direction. Free-floating and branching points have no normal direction, so they receive a random-angled offset regardless of this setting.
|
/// Whether to offset anchor points along their normal direction (perpendicular to the path) or in a random direction. Free-floating and branching points have no normal direction, so they receive a random-angled offset regardless of this setting.
|
||||||
#[default(true)]
|
#[default(true)]
|
||||||
along_normals: bool,
|
along_normals: bool,
|
||||||
) -> (Vector, Attr<TransformAttr>) {
|
) -> Result<(V::Live<'e>, Attr<TransformAttr>), Interrupt> {
|
||||||
|
let (content, lane_transform) = map_vectors(ctx.arena(), content, *lane_transform, |element, transform| {
|
||||||
let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into());
|
let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into());
|
||||||
let inverse_linear = inverse_linear_or_repair(transform.matrix2);
|
let inverse_linear = inverse_linear_or_repair(transform.matrix2);
|
||||||
|
|
||||||
@@ -2775,23 +2887,28 @@ fn jitter_points(
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let mut element = element;
|
let mut element = element;
|
||||||
apply_point_deltas(&mut element, &deltas, *transform);
|
apply_point_deltas(&mut element, &deltas, transform);
|
||||||
|
|
||||||
(element, Attr(*transform))
|
(element, transform)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok((content, Attr(lane_transform)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Displaces anchor points along their normal direction (perpendicular to the path) by a set distance.
|
/// Displaces anchor points along their normal direction (perpendicular to the path) by a set distance.
|
||||||
/// Points with 0 or 3+ segment connections have no well-defined normal and are left in place.
|
/// Points with 0 or 3+ segment connections have no well-defined normal and are left in place.
|
||||||
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
|
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
|
||||||
fn offset_points(
|
fn offset_points<'e, V: MapVectorContent + Clone + Send + Sync + CacheHash + 'static>(
|
||||||
_: impl Ctx,
|
ctx: impl Ctx + ExtractArena<'e>,
|
||||||
/// The vector geometry with points to be offset.
|
/// The vector geometry with points to be offset.
|
||||||
(mut element, transform): (Vector, Attr<TransformAttr>),
|
#[implementations(Graphic, Vector)]
|
||||||
|
(content, lane_transform): (V, Attr<TransformAttr>),
|
||||||
/// The distance to offset each anchor point along its normal. Positive values move outward, negative values move inward.
|
/// The distance to offset each anchor point along its normal. Positive values move outward, negative values move inward.
|
||||||
#[default(10.)]
|
#[default(10.)]
|
||||||
#[unit(" px")]
|
#[unit(" px")]
|
||||||
distance: f64,
|
distance: f64,
|
||||||
) -> (Vector, Attr<TransformAttr>) {
|
) -> Result<(V::Live<'e>, Attr<TransformAttr>), Interrupt> {
|
||||||
|
let (content, lane_transform) = map_vectors(ctx.arena(), content, *lane_transform, |mut element, transform| {
|
||||||
let inverse_linear = inverse_linear_or_repair(transform.matrix2);
|
let inverse_linear = inverse_linear_or_repair(transform.matrix2);
|
||||||
|
|
||||||
let deltas: Vec<_> = (0..element.point_domain.positions().len())
|
let deltas: Vec<_> = (0..element.point_domain.positions().len())
|
||||||
@@ -2804,9 +2921,12 @@ fn offset_points(
|
|||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
apply_point_deltas(&mut element, &deltas, *transform);
|
apply_point_deltas(&mut element, &deltas, transform);
|
||||||
|
|
||||||
(element, Attr(*transform))
|
(element, transform)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok((content, Attr(lane_transform)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Interpolates the geometry, appearance, and transform between multiple vector layers, producing a single morphed vector shape.
|
/// Interpolates the geometry, appearance, and transform between multiple vector layers, producing a single morphed vector shape.
|
||||||
@@ -3805,14 +3925,25 @@ fn bevel_algorithm(mut vector: Vector, transform: DAffine2, distance: f64) -> Ve
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
|
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
|
||||||
fn bevel(_: impl Ctx, (element, transform): (Vector, Attr<TransformAttr>), #[default(10.)] distance: Length) -> (Vector, Attr<TransformAttr>) {
|
fn bevel<'e, V: MapVectorContent + Clone + Send + Sync + CacheHash + 'static>(
|
||||||
(bevel_algorithm(element, *transform, distance), Attr(*transform))
|
ctx: impl Ctx + ExtractArena<'e>,
|
||||||
|
#[implementations(Graphic, Vector)] (content, lane_transform): (V, Attr<TransformAttr>),
|
||||||
|
#[default(10.)] distance: Length,
|
||||||
|
) -> Result<(V::Live<'e>, Attr<TransformAttr>), Interrupt> {
|
||||||
|
let (content, lane_transform) = map_vectors(ctx.arena(), content, *lane_transform, |element, transform| (bevel_algorithm(element, transform, distance), transform))?;
|
||||||
|
|
||||||
|
Ok((content, Attr(lane_transform)))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
|
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
|
||||||
fn close_path(_: impl Ctx, mut source: Vector) -> Vector {
|
fn close_path<'e, V: MapVectorContent + Clone + Send + Sync + CacheHash + 'static>(ctx: impl Ctx + ExtractArena<'e>, #[implementations(Graphic, Vector)] content: V) -> Result<V::Live<'e>, Interrupt> {
|
||||||
|
let (content, _) = map_vectors(ctx.arena(), content, DAffine2::IDENTITY, |mut source, transform| {
|
||||||
source.close_subpaths();
|
source.close_subpaths();
|
||||||
source
|
|
||||||
|
(source, transform)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Vector: Measure"), path(core_types::vector))]
|
#[node_macro::node(category("Vector: Measure"), path(core_types::vector))]
|
||||||
@@ -3950,7 +4081,6 @@ fn centroid(_: impl Ctx, vector: IList<Vector>, centroid_type: CentroidType) ->
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
use core_types::transform::Footprint;
|
|
||||||
use graphic_types::graphic::paint_graphics;
|
use graphic_types::graphic::paint_graphics;
|
||||||
use kurbo::{CubicBez, Ellipse, Point, Rect};
|
use kurbo::{CubicBez, Ellipse, Point, Rect};
|
||||||
use vector_types::vector::algorithms::bezpath_algorithms::{TValue, trim_pathseg};
|
use vector_types::vector::algorithms::bezpath_algorithms::{TValue, trim_pathseg};
|
||||||
@@ -3969,14 +4099,24 @@ mod test {
|
|||||||
vector
|
vector
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Runs an element-wise modifier kernel, which now takes the serving arena and
|
||||||
|
/// hands back the mapped content beside its transform.
|
||||||
|
fn with_ctx<R>(run: impl FnOnce(&core_types::context::ContextImpl<'_>) -> R) -> R {
|
||||||
|
let arena = core_types::arena::Arena::new(1 << 16).unwrap();
|
||||||
|
let scope = core_types::record::test_fixtures::scope_fixture(&[], &arena);
|
||||||
|
let ctx = core_types::context::ContextImpl::root(&scope);
|
||||||
|
|
||||||
|
run(&ctx)
|
||||||
|
}
|
||||||
|
|
||||||
const SQUARE_WITH_CENTER: [DVec2; 5] = [DVec2::new(0., 0.), DVec2::new(10., 0.), DVec2::new(10., 10.), DVec2::new(0., 10.), DVec2::new(5., 5.)];
|
const SQUARE_WITH_CENTER: [DVec2; 5] = [DVec2::new(0., 0.), DVec2::new(10., 0.), DVec2::new(10., 10.), DVec2::new(0., 10.), DVec2::new(5., 5.)];
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn offset_path_does_not_duplicate_closing_anchors() {
|
fn offset_path_does_not_duplicate_closing_anchors() {
|
||||||
// Offsetting closed triangles must not leave each subpath with a redundant start/end anchor (a Kurbo offset
|
// Offsetting closed triangles must not leave each subpath with a redundant start/end anchor (a Kurbo offset
|
||||||
// contour returns to approximately, not exactly, its start; that near-coincident point must close, not duplicate).
|
// contour returns to approximately, not exactly, its start; that near-coincident point must close, not duplicate).
|
||||||
let delaunay = super::triangulate(&(), vector_from_points(&SQUARE_WITH_CENTER), false);
|
let delaunay = with_ctx(|ctx| super::triangulate(ctx, vector_from_points(&SQUARE_WITH_CENTER), false).unwrap());
|
||||||
let (result, _) = super::offset_path(&(), (delaunay, Attr(DAffine2::IDENTITY)), 0.5, StrokeJoin::Miter, 4.);
|
let result = with_ctx(|ctx| super::offset_path(ctx, (delaunay, Attr(DAffine2::IDENTITY)), 0.5, StrokeJoin::Miter, 4.).unwrap().0);
|
||||||
|
|
||||||
let mut subpaths = 0;
|
let mut subpaths = 0;
|
||||||
for (group, closed) in result.stroke_manipulator_groups() {
|
for (group, closed) in result.stroke_manipulator_groups() {
|
||||||
@@ -3991,7 +4131,7 @@ mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn delaunay_disconnected_cells_make_one_region_per_triangle() {
|
fn delaunay_disconnected_cells_make_one_region_per_triangle() {
|
||||||
let vector = super::triangulate(&(), vector_from_points(&SQUARE_WITH_CENTER), false);
|
let vector = with_ctx(|ctx| super::triangulate(ctx, vector_from_points(&SQUARE_WITH_CENTER), false).unwrap());
|
||||||
// The square plus its center tessellates into four triangles, each its own closed subpath.
|
// The square plus its center tessellates into four triangles, each its own closed subpath.
|
||||||
assert_eq!(vector.region_domain.ids().len(), 4);
|
assert_eq!(vector.region_domain.ids().len(), 4);
|
||||||
assert_eq!(vector.segment_domain.ids().len(), 4 * 3);
|
assert_eq!(vector.segment_domain.ids().len(), 4 * 3);
|
||||||
@@ -4019,8 +4159,8 @@ mod test {
|
|||||||
assert_eq!(subpath_winding_signs(&ellipse)[0], expected, "Rectangle and Ellipse should agree on winding");
|
assert_eq!(subpath_winding_signs(&ellipse)[0], expected, "Rectangle and Ellipse should agree on winding");
|
||||||
|
|
||||||
// Delaunay and Voronoi must emit subpaths that wind the same way as those generators.
|
// Delaunay and Voronoi must emit subpaths that wind the same way as those generators.
|
||||||
let delaunay = super::triangulate(&(), vector_from_points(&SQUARE_WITH_CENTER), false);
|
let delaunay = with_ctx(|ctx| super::triangulate(ctx, vector_from_points(&SQUARE_WITH_CENTER), false).unwrap());
|
||||||
let voronoi = super::voronoi_cells(&(), vector_from_points(&SQUARE_WITH_CENTER), false);
|
let voronoi = with_ctx(|ctx| super::voronoi_cells(ctx, vector_from_points(&SQUARE_WITH_CENTER), false).unwrap());
|
||||||
for sign in subpath_winding_signs(&delaunay) {
|
for sign in subpath_winding_signs(&delaunay) {
|
||||||
assert_eq!(sign, expected, "Delaunay subpath winding should match the Rectangle/Ellipse generators");
|
assert_eq!(sign, expected, "Delaunay subpath winding should match the Rectangle/Ellipse generators");
|
||||||
}
|
}
|
||||||
@@ -4031,7 +4171,7 @@ mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn delaunay_shared_mesh_welds_points_and_shares_edges() {
|
fn delaunay_shared_mesh_welds_points_and_shares_edges() {
|
||||||
let vector = super::triangulate(&(), vector_from_points(&SQUARE_WITH_CENTER), true);
|
let vector = with_ctx(|ctx| super::triangulate(ctx, vector_from_points(&SQUARE_WITH_CENTER), true).unwrap());
|
||||||
// The connected mesh reuses the five input points and shares edges, with no fillable regions.
|
// The connected mesh reuses the five input points and shares edges, with no fillable regions.
|
||||||
assert_eq!(vector.region_domain.ids().len(), 0);
|
assert_eq!(vector.region_domain.ids().len(), 0);
|
||||||
assert_eq!(vector.point_domain.ids().len(), 5);
|
assert_eq!(vector.point_domain.ids().len(), 5);
|
||||||
@@ -4041,7 +4181,7 @@ mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn voronoi_disconnected_cells_make_a_region_per_cell() {
|
fn voronoi_disconnected_cells_make_a_region_per_cell() {
|
||||||
let vector = super::voronoi_cells(&(), vector_from_points(&SQUARE_WITH_CENTER), false);
|
let vector = with_ctx(|ctx| super::voronoi_cells(ctx, vector_from_points(&SQUARE_WITH_CENTER), false).unwrap());
|
||||||
let regions = vector.region_domain.ids().len();
|
let regions = vector.region_domain.ids().len();
|
||||||
assert!(regions > 0, "expected at least one Voronoi region");
|
assert!(regions > 0, "expected at least one Voronoi region");
|
||||||
// Every region is a closed subpath, so segments and points come in matched per-region loops.
|
// Every region is a closed subpath, so segments and points come in matched per-region loops.
|
||||||
@@ -4056,7 +4196,7 @@ mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn voronoi_shared_mesh_has_no_regions() {
|
fn voronoi_shared_mesh_has_no_regions() {
|
||||||
let vector = super::voronoi_cells(&(), vector_from_points(&SQUARE_WITH_CENTER), true);
|
let vector = with_ctx(|ctx| super::voronoi_cells(ctx, vector_from_points(&SQUARE_WITH_CENTER), true).unwrap());
|
||||||
assert_eq!(vector.region_domain.ids().len(), 0);
|
assert_eq!(vector.region_domain.ids().len(), 0);
|
||||||
assert!(vector.segment_domain.ids().len() > 0);
|
assert!(vector.segment_domain.ids().len() > 0);
|
||||||
}
|
}
|
||||||
@@ -4065,7 +4205,7 @@ mod test {
|
|||||||
fn voronoi_leaves_degenerate_input_untouched() {
|
fn voronoi_leaves_degenerate_input_untouched() {
|
||||||
// Two points cannot form a diagram, so the element passes through unchanged.
|
// Two points cannot form a diagram, so the element passes through unchanged.
|
||||||
let points = [DVec2::new(0., 0.), DVec2::new(1., 1.)];
|
let points = [DVec2::new(0., 0.), DVec2::new(1., 1.)];
|
||||||
let vector = super::voronoi_cells(&(), vector_from_points(&points), false);
|
let vector = with_ctx(|ctx| super::voronoi_cells(ctx, vector_from_points(&points), false).unwrap());
|
||||||
assert_eq!(vector.point_domain.ids().len(), 2);
|
assert_eq!(vector.point_domain.ids().len(), 2);
|
||||||
assert_eq!(vector.segment_domain.ids().len(), 0);
|
assert_eq!(vector.segment_domain.ids().len(), 0);
|
||||||
}
|
}
|
||||||
@@ -4081,7 +4221,7 @@ mod test {
|
|||||||
DVec2::new(3., 4.),
|
DVec2::new(3., 4.),
|
||||||
DVec2::new(7., 5.),
|
DVec2::new(7., 5.),
|
||||||
];
|
];
|
||||||
let vector = super::relax_points(&(), vector_from_points(&points), 2.);
|
let vector = with_ctx(|ctx| super::relax_points(ctx, vector_from_points(&points), 2.).unwrap());
|
||||||
|
|
||||||
// Relaxation preserves the point count but repositions the interior anchors within the hull.
|
// Relaxation preserves the point count but repositions the interior anchors within the hull.
|
||||||
assert_eq!(vector.point_domain.ids().len(), points.len());
|
assert_eq!(vector.point_domain.ids().len(), points.len());
|
||||||
@@ -4098,7 +4238,7 @@ mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn bounding_box() {
|
fn bounding_box() {
|
||||||
let bounding_box = super::bounding_box(&(), Vector::from_bezpath(Rect::new(-1., -1., 1., 1.).to_path(DEFAULT_ACCURACY)));
|
let bounding_box = with_ctx(|ctx| super::bounding_box(ctx, Vector::from_bezpath(Rect::new(-1., -1., 1., 1.).to_path(DEFAULT_ACCURACY))).unwrap());
|
||||||
assert_eq!(bounding_box.region_manipulator_groups().count(), 1);
|
assert_eq!(bounding_box.region_manipulator_groups().count(), 1);
|
||||||
let manipulator_groups_anchors = bounding_box
|
let manipulator_groups_anchors = bounding_box
|
||||||
.region_manipulator_groups()
|
.region_manipulator_groups()
|
||||||
@@ -4113,7 +4253,7 @@ mod test {
|
|||||||
|
|
||||||
// The box spans local space, so a lane rotation leaves it unchanged
|
// The box spans local space, so a lane rotation leaves it unchanged
|
||||||
let square = Vector::from_bezpath(Rect::new(-1., -1., 1., 1.).to_path(DEFAULT_ACCURACY));
|
let square = Vector::from_bezpath(Rect::new(-1., -1., 1., 1.).to_path(DEFAULT_ACCURACY));
|
||||||
let bounding_box = super::bounding_box(&(), square);
|
let bounding_box = with_ctx(|ctx| super::bounding_box(ctx, square).unwrap());
|
||||||
assert_eq!(bounding_box.region_manipulator_groups().count(), 1);
|
assert_eq!(bounding_box.region_manipulator_groups().count(), 1);
|
||||||
let manipulator_groups_anchors = bounding_box
|
let manipulator_groups_anchors = bounding_box
|
||||||
.region_manipulator_groups()
|
.region_manipulator_groups()
|
||||||
@@ -4132,16 +4272,11 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn sample_polyline() {
|
fn sample_polyline() {
|
||||||
let path = BezPath::from_vec(vec![PathEl::MoveTo(Point::ZERO), PathEl::CurveTo(Point::ZERO, Point::new(100., 0.), Point::new(100., 0.))]);
|
let path = BezPath::from_vec(vec![PathEl::MoveTo(Point::ZERO), PathEl::CurveTo(Point::ZERO, Point::new(100., 0.), Point::new(100., 0.))]);
|
||||||
let (sample_polyline, _) = super::sample_polyline(
|
let sample_polyline = with_ctx(|ctx| {
|
||||||
&Footprint::default(),
|
super::sample_polyline(ctx, (Vector::from_bezpath(path), Attr(DAffine2::IDENTITY)), PointSpacingType::Separation, 30., 0, 0., 0., false)
|
||||||
(Vector::from_bezpath(path), Attr(DAffine2::IDENTITY)),
|
.unwrap()
|
||||||
PointSpacingType::Separation,
|
.0
|
||||||
30.,
|
});
|
||||||
0,
|
|
||||||
0.,
|
|
||||||
0.,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
let sample_polyline = &sample_polyline;
|
let sample_polyline = &sample_polyline;
|
||||||
assert_eq!(sample_polyline.point_domain.positions().len(), 4);
|
assert_eq!(sample_polyline.point_domain.positions().len(), 4);
|
||||||
for (pos, expected) in sample_polyline.point_domain.positions().iter().zip([DVec2::X * 0., DVec2::X * 30., DVec2::X * 60., DVec2::X * 90.]) {
|
for (pos, expected) in sample_polyline.point_domain.positions().iter().zip([DVec2::X * 0., DVec2::X * 30., DVec2::X * 60., DVec2::X * 90.]) {
|
||||||
@@ -4151,16 +4286,11 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn sample_polyline_adaptive_spacing() {
|
fn sample_polyline_adaptive_spacing() {
|
||||||
let path = BezPath::from_vec(vec![PathEl::MoveTo(Point::ZERO), PathEl::CurveTo(Point::ZERO, Point::new(100., 0.), Point::new(100., 0.))]);
|
let path = BezPath::from_vec(vec![PathEl::MoveTo(Point::ZERO), PathEl::CurveTo(Point::ZERO, Point::new(100., 0.), Point::new(100., 0.))]);
|
||||||
let (sample_polyline, _) = super::sample_polyline(
|
let sample_polyline = with_ctx(|ctx| {
|
||||||
&Footprint::default(),
|
super::sample_polyline(ctx, (Vector::from_bezpath(path), Attr(DAffine2::IDENTITY)), PointSpacingType::Separation, 18., 0, 45., 10., true)
|
||||||
(Vector::from_bezpath(path), Attr(DAffine2::IDENTITY)),
|
.unwrap()
|
||||||
PointSpacingType::Separation,
|
.0
|
||||||
18.,
|
});
|
||||||
0,
|
|
||||||
45.,
|
|
||||||
10.,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
let sample_polyline = &sample_polyline;
|
let sample_polyline = &sample_polyline;
|
||||||
assert_eq!(sample_polyline.point_domain.positions().len(), 4);
|
assert_eq!(sample_polyline.point_domain.positions().len(), 4);
|
||||||
for (pos, expected) in sample_polyline.point_domain.positions().iter().zip([DVec2::X * 45., DVec2::X * 60., DVec2::X * 75., DVec2::X * 90.]) {
|
for (pos, expected) in sample_polyline.point_domain.positions().iter().zip([DVec2::X * 45., DVec2::X * 60., DVec2::X * 75., DVec2::X * 90.]) {
|
||||||
@@ -4169,12 +4299,15 @@ mod test {
|
|||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn poisson() {
|
fn poisson() {
|
||||||
let poisson_points = super::scatter_points(
|
let poisson_points = with_ctx(|ctx| {
|
||||||
&Footprint::default(),
|
super::scatter_points(
|
||||||
|
ctx,
|
||||||
Vector::from_bezpath(Ellipse::from_rect(Rect::new(-50., -50., 50., 50.)).to_path(DEFAULT_ACCURACY)),
|
Vector::from_bezpath(Ellipse::from_rect(Rect::new(-50., -50., 50., 50.)).to_path(DEFAULT_ACCURACY)),
|
||||||
10. * std::f64::consts::SQRT_2,
|
10. * std::f64::consts::SQRT_2,
|
||||||
0,
|
0,
|
||||||
);
|
)
|
||||||
|
.unwrap()
|
||||||
|
});
|
||||||
let poisson_points = &poisson_points;
|
let poisson_points = &poisson_points;
|
||||||
assert!(
|
assert!(
|
||||||
(20..=40).contains(&poisson_points.point_domain.positions().len()),
|
(20..=40).contains(&poisson_points.point_domain.positions().len()),
|
||||||
@@ -4211,7 +4344,7 @@ mod test {
|
|||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn spline() {
|
fn spline() {
|
||||||
let spline = super::spline(&Footprint::default(), Vector::from_bezpath(Rect::new(0., 0., 100., 100.).to_path(DEFAULT_ACCURACY)));
|
let spline = with_ctx(|ctx| super::spline(ctx, Vector::from_bezpath(Rect::new(0., 0., 100., 100.).to_path(DEFAULT_ACCURACY))).unwrap());
|
||||||
let spline = &spline;
|
let spline = &spline;
|
||||||
assert_eq!(spline.stroke_bezpath_iter().count(), 1);
|
assert_eq!(spline.stroke_bezpath_iter().count(), 1);
|
||||||
assert_eq!(spline.point_domain.positions(), &[DVec2::ZERO, DVec2::new(100., 0.), DVec2::new(100., 100.), DVec2::new(0., 100.)]);
|
assert_eq!(spline.point_domain.positions(), &[DVec2::ZERO, DVec2::new(100., 0.), DVec2::new(100., 100.), DVec2::new(0., 100.)]);
|
||||||
@@ -4284,7 +4417,7 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn bevel_rect() {
|
fn bevel_rect() {
|
||||||
let source = Rect::new(0., 0., 100., 100.).to_path(DEFAULT_ACCURACY);
|
let source = Rect::new(0., 0., 100., 100.).to_path(DEFAULT_ACCURACY);
|
||||||
let (beveled, _) = super::bevel(&Footprint::default(), (Vector::from_bezpath(source), Attr(DAffine2::IDENTITY)), 2_f64.sqrt() * 10.);
|
let beveled = with_ctx(|ctx| super::bevel(ctx, (Vector::from_bezpath(source), Attr(DAffine2::IDENTITY)), 2_f64.sqrt() * 10.).unwrap().0);
|
||||||
let beveled = &beveled;
|
let beveled = &beveled;
|
||||||
|
|
||||||
assert_eq!(beveled.point_domain.positions().len(), 8);
|
assert_eq!(beveled.point_domain.positions().len(), 8);
|
||||||
@@ -4312,7 +4445,7 @@ mod test {
|
|||||||
source.line_to(Point::ZERO);
|
source.line_to(Point::ZERO);
|
||||||
source.push(curve.as_path_el());
|
source.push(curve.as_path_el());
|
||||||
|
|
||||||
let (beveled, _) = super::bevel(&(), (Vector::from_bezpath(source), Attr(DAffine2::IDENTITY)), 2_f64.sqrt() * 10.);
|
let beveled = with_ctx(|ctx| super::bevel(ctx, (Vector::from_bezpath(source), Attr(DAffine2::IDENTITY)), 2_f64.sqrt() * 10.).unwrap().0);
|
||||||
let beveled = &beveled;
|
let beveled = &beveled;
|
||||||
|
|
||||||
assert_eq!(beveled.point_domain.positions().len(), 4);
|
assert_eq!(beveled.point_domain.positions().len(), 4);
|
||||||
@@ -4339,7 +4472,7 @@ mod test {
|
|||||||
// The legacy test set the transform on a list it never passed, so the
|
// The legacy test set the transform on a list it never passed, so the
|
||||||
// evaluated lane used the identity; keep that behavior explicit.
|
// evaluated lane used the identity; keep that behavior explicit.
|
||||||
let vector = Vector::from_bezpath(source);
|
let vector = Vector::from_bezpath(source);
|
||||||
let (beveled, _) = super::bevel(&(), (vector, Attr(DAffine2::IDENTITY)), 2_f64.sqrt() * 10.);
|
let beveled = with_ctx(|ctx| super::bevel(ctx, (vector, Attr(DAffine2::IDENTITY)), 2_f64.sqrt() * 10.).unwrap().0);
|
||||||
let beveled = &beveled;
|
let beveled = &beveled;
|
||||||
|
|
||||||
assert_eq!(beveled.point_domain.positions().len(), 4);
|
assert_eq!(beveled.point_domain.positions().len(), 4);
|
||||||
@@ -4362,7 +4495,7 @@ mod test {
|
|||||||
source.line_to(Point::new(100., 100.));
|
source.line_to(Point::new(100., 100.));
|
||||||
source.line_to(Point::new(0., 100.));
|
source.line_to(Point::new(0., 100.));
|
||||||
|
|
||||||
let (beveled, _) = super::bevel(&Footprint::default(), (Vector::from_bezpath(source), Attr(DAffine2::IDENTITY)), 999.);
|
let beveled = with_ctx(|ctx| super::bevel(ctx, (Vector::from_bezpath(source), Attr(DAffine2::IDENTITY)), 999.).unwrap().0);
|
||||||
let beveled = &beveled;
|
let beveled = &beveled;
|
||||||
|
|
||||||
assert_eq!(beveled.point_domain.positions().len(), 6);
|
assert_eq!(beveled.point_domain.positions().len(), 6);
|
||||||
@@ -4386,10 +4519,26 @@ mod test {
|
|||||||
|
|
||||||
let subpath = BezPath::from_path_segments([line, point, curve].into_iter());
|
let subpath = BezPath::from_path_segments([line, point, curve].into_iter());
|
||||||
|
|
||||||
let (beveled, _) = super::bevel(&Footprint::default(), (Vector::from_bezpath(subpath), Attr(DAffine2::IDENTITY)), 5.);
|
let beveled = with_ctx(|ctx| super::bevel(ctx, (Vector::from_bezpath(subpath), Attr(DAffine2::IDENTITY)), 5.).unwrap().0);
|
||||||
let beveled = &beveled;
|
let beveled = &beveled;
|
||||||
|
|
||||||
assert_eq!(beveled.point_domain.positions().len(), 6);
|
assert_eq!(beveled.point_domain.positions().len(), 6);
|
||||||
assert_eq!(beveled.segment_domain.ids().len(), 5);
|
assert_eq!(beveled.segment_domain.ids().len(), 5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod registry_tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// An element-wise modifier registers a row per content type, so a graphic
|
||||||
|
/// wire reaches the same kernel as a bare vector one.
|
||||||
|
#[test]
|
||||||
|
fn a_modifier_rows_both_its_content_types() {
|
||||||
|
let entries = _round_corners_mod::round_corners_entries();
|
||||||
|
assert_eq!(entries.len(), 2, "one row per content type");
|
||||||
|
assert_eq!(entries[0].io.inputs[0], core_types::registry::record_source_type::<graphic_types::Graphic>());
|
||||||
|
assert_eq!(entries[0].io.return_value, core_types::registry::record_type::<graphic_types::Graphic>());
|
||||||
|
assert_eq!(entries[1].io.inputs[0], core_types::registry::record_source_type::<Vector>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user