Convert cut_path, separate_subpaths, and path_is_closed to fold kernels

This commit is contained in:
Dennis Kobert
2026-08-23 09:56:09 +00:00
parent 9d2031f7d0
commit 73b2ef1d58
+100 -22
View File
@@ -1602,8 +1602,7 @@ fn solidify_stroke_vector_extent(content: ListIn<'_, Vector>, level: LevelIn) ->
pub use _solidify_stroke_vector_mod::solidify_stroke_vector_entries; pub use _solidify_stroke_vector_mod::solidify_stroke_vector_entries;
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))] fn separate_subpaths_core(content: List<Vector>) -> List<Vector> {
fn separate_subpaths(_: impl Ctx, content: List<Vector>) -> List<Vector> {
content content
.into_iter() .into_iter()
.flat_map(|row| { .flat_map(|row| {
@@ -1632,22 +1631,63 @@ fn separate_subpaths(_: impl Ctx, content: List<Vector>) -> List<Vector> {
.collect() .collect()
} }
/// Splits each vector element into one element per subpath, keeping the source element's attributes on every split-off lane.
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector), extent(separate_subpaths_extent))]
fn separate_subpaths<'e>(
ctx: impl Ctx + ExtractArena<'e> + ExtractIndex + InjectIndex + Copy,
content: IList<Vector>,
) -> Result<
IList<(
Vector,
Attr<'e, TransformAttr>,
Attr<'e, Fill>,
Attr<'e, StrokeAttr>,
Attr<'e, BlendModeAttr>,
Attr<'e, Opacity>,
Attr<'e, OpacityFill>,
Attr<'e, ClippingMask>,
Attr<'e, EditorLayerPath>,
Attr<'e, EditorMergedLayers>,
)>,
Interrupt,
> {
let output = separate_subpaths_core(legacy_vector_list_of(content));
emit_legacy_lane(ctx.arena(), output, ctx.innermost_index() as usize)
}
/// A row splits into one lane per subpath, so the count depends on the
/// content: the level reports the subject's count as a lower bound and
/// consumers drain to the past-end signal.
fn separate_subpaths_extent(content: ListIn<'_, Vector>, level: LevelIn) -> GPoll<Extent> {
match level.top() {
true => content.total().map(|total| {
Extent::AtLeast(match total {
Extent::Exactly(count) | Extent::AtLeast(count) => count,
Extent::Free => 0,
})
}),
false => GPoll::Final(Extent::Exactly(1)),
}
}
/// Determines if the subpath at the given index (across all vector element subpaths) is closed, meaning its ends are connected together forming a loop. /// Determines if the subpath at the given index (across all vector element subpaths) is closed, meaning its ends are connected together forming a loop.
#[node_macro::node(name("Path is Closed"), category("Vector: Measure"), path(core_types::vector))] #[node_macro::node(name("Path is Closed"), category("Vector: Measure"), path(core_types::vector))]
fn path_is_closed( fn path_is_closed(
_: impl Ctx, _: impl Ctx + ExtractIndex + InjectIndex + Copy,
/// The vector content whose subpaths are inspected. /// The vector content whose subpaths are inspected.
content: List<Vector>, content: IList<Vector>,
/// The index of the subpath to check, counting across subpaths in all vector elements. /// The index of the subpath to check, counting across subpaths in all vector elements.
index: f64, index: f64,
) -> bool { ) -> bool {
content (0..content.len())
.iter_element_values() .flat_map(|row| content.element_ref(row).build_stroke_path_iter().map(|(_, closed)| closed))
.flat_map(|vector| vector.build_stroke_path_iter().map(|(_, closed)| closed))
.nth(index.max(0.) as usize) .nth(index.max(0.) as usize)
.unwrap_or(false) .unwrap_or(false)
} }
// Converts with the write-attribute family: the record form needs a lazy
// value input on a record node, which the macro does not accept yet, and it
// shares that family's per-item index convention.
#[node_macro::node(category("Vector"), path(graphene_core::vector))] #[node_macro::node(category("Vector"), path(graphene_core::vector))]
fn map_points(ctx: impl Ctx + DeriveCtx, content: List<Vector>, mapped: impl Node<Context<'_>, Output = DVec2>) -> Result<List<Vector>, Interrupt> { fn map_points(ctx: impl Ctx + DeriveCtx, content: List<Vector>, mapped: impl Node<Context<'_>, Output = DVec2>) -> Result<List<Vector>, Interrupt> {
let spilled = ctx.index_head(); let spilled = ctx.index_head();
@@ -2027,21 +2067,22 @@ fn decimate(
(result, Attr(transform_attribute)) (result, Attr(transform_attribute))
} }
/// Cuts a path at a given progression from 0 to 1 along the path, creating two new subpaths from the original one (if the path is initially open) or one open subpath (if the path is initially closed). /// The materialized vector level as the legacy list the cross-lane cores walk.
/// fn legacy_vector_list_of(content: core_types::node::List<'_, Vector>) -> List<Vector> {
/// If multiple subpaths make up the path, the whole number part of the progression value selects the subpath and the decimal part determines the position along it. // SAFETY: a materialized input's frames are arena-resident.
#[node_macro::node(category("Vector: Modifier"), path(graphene_core::vector))] let item = unsafe { core_types::record::GroupItem::from_resident(content.batch()) };
fn cut_path( graphic_types::graphic::run_to_render_list::<Vector>(&item).expect("the run holds the row's element type")
_: impl Ctx, }
/// The path to insert a cut into.
mut content: List<Vector>, /// A count-preserving cross-lane node's extent: the subject's own counts.
/// The factor from the start to the end of the path, 0–1 for one subpath, 1–2 for a second subpath, and so on. fn subject_counts_extent(content: ListIn<'_, Vector>, level: LevelIn) -> GPoll<Extent> {
progression: Progression, match level.top() {
/// Swap the direction of the path. true => content.total(),
reverse: bool, false => GPoll::Final(Extent::Exactly(1)),
/// Traverse the path using each segment's Bézier curve parameterization instead of the Euclidean distance. Faster to compute but doesn't respect actual distances. }
parameterized_distance: bool, }
) -> List<Vector> {
fn cut_path_core(mut content: List<Vector>, progression: f64, reverse: bool, parameterized_distance: bool) -> List<Vector> {
let euclidian = !parameterized_distance; let euclidian = !parameterized_distance;
let bezpaths = content let bezpaths = content
@@ -2080,6 +2121,43 @@ fn cut_path(
content content
} }
/// Cuts a path at a given progression from 0 to 1 along the path, creating two new subpaths from the original one (if the path is initially open) or one open subpath (if the path is initially closed).
///
/// If multiple subpaths make up the path, the whole number part of the progression value selects the subpath and the decimal part determines the position along it.
#[node_macro::node(category("Vector: Modifier"), path(graphene_core::vector), extent(cut_path_extent))]
fn cut_path<'e>(
ctx: impl Ctx + ExtractArena<'e> + ExtractIndex + InjectIndex + Copy,
/// The path to insert a cut into.
content: IList<Vector>,
/// The factor from the start to the end of the path, 0–1 for one subpath, 1–2 for a second subpath, and so on.
progression: Progression,
/// Swap the direction of the path.
reverse: bool,
/// Traverse the path using each segment's Bézier curve parameterization instead of the Euclidean distance. Faster to compute but doesn't respect actual distances.
parameterized_distance: bool,
) -> Result<
IList<(
Vector,
Attr<'e, TransformAttr>,
Attr<'e, Fill>,
Attr<'e, StrokeAttr>,
Attr<'e, BlendModeAttr>,
Attr<'e, Opacity>,
Attr<'e, OpacityFill>,
Attr<'e, ClippingMask>,
Attr<'e, EditorLayerPath>,
Attr<'e, EditorMergedLayers>,
)>,
Interrupt,
> {
let output = cut_path_core(legacy_vector_list_of(content), progression, reverse, parameterized_distance);
emit_legacy_lane(ctx.arena(), output, ctx.innermost_index() as usize)
}
fn cut_path_extent(content: ListIn<'_, Vector>, _progression: ValueIn<'_, f64>, _reverse: ValueIn<'_, bool>, _parameterized_distance: ValueIn<'_, bool>, level: LevelIn) -> GPoll<Extent> {
subject_counts_extent(content, level)
}
/// 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(_: impl Ctx, mut content: Vector) -> Vector {