mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
Bridge legacy seams and convert the transform family
This commit is contained in:
@@ -109,6 +109,22 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, Vec<RegistryEntry>> {
|
||||
// MEMO NODES
|
||||
// ==========
|
||||
];
|
||||
// The transform's value-typed rows, served by `transform_value` under the
|
||||
// leveled transform's identifier.
|
||||
node_types.extend(
|
||||
graphene_std::transform_nodes::transform_nodes::transform_value_entries()
|
||||
.into_iter()
|
||||
.map(|entry| (graphene_std::transform_nodes::transform_nodes::transform::IDENTIFIER.clone(), entry)),
|
||||
);
|
||||
// The transitional level bridge: a leveled wire materializes into the legacy
|
||||
// list an unconverted consumer expects. The rows are keyed under the legacy
|
||||
// convert identifiers and die with the last legacy consumer.
|
||||
node_types.extend(
|
||||
graphene_std::graphic::level_to_list_entries()
|
||||
.into_iter()
|
||||
.zip(["List<Graphic>", "List<Vector>", "List<Raster<CPU>>", "List<Raster<GPU>>", "List<Color>", "List<GradientStops>", "List<String>"])
|
||||
.map(|(entry, target)| (ProtoNodeIdentifier::with_owned_string(format!("graphene_core::ops::ConvertNode<{target}>")), entry)),
|
||||
);
|
||||
// =============
|
||||
// CONVERT NODES
|
||||
// =============
|
||||
|
||||
@@ -581,15 +581,49 @@ fn wrap_graphic_extent(_content: ListIn<'_, Graphic>, _level: LevelIn) -> GPoll<
|
||||
GPoll::Final(Extent::Exactly(1))
|
||||
}
|
||||
|
||||
/// Converts the level's elements into `Graphic` elements. A `Graphic` level passes through unchanged.
|
||||
/// Converts the level's elements into `Graphic` elements. A `Graphic` level passes through
|
||||
/// unchanged. The legacy list rows accept an unconverted producer's list value as one element.
|
||||
#[node_macro::node(category("General"))]
|
||||
pub fn to_graphic<T: Into<Graphic> + Clone + Send + Sync + core_types::CacheHash + 'static>(
|
||||
_: impl Ctx,
|
||||
#[implementations(Graphic, Vector, Raster<CPU>, Raster<GPU>, Color, GradientStops, String)] content: T,
|
||||
#[implementations(
|
||||
Graphic,
|
||||
Vector,
|
||||
Raster<CPU>,
|
||||
Raster<GPU>,
|
||||
Color,
|
||||
GradientStops,
|
||||
String,
|
||||
List<Graphic>,
|
||||
List<Vector>,
|
||||
List<Raster<CPU>>,
|
||||
List<Raster<GPU>>,
|
||||
List<Color>,
|
||||
List<GradientStops>,
|
||||
List<String>,
|
||||
)]
|
||||
content: T,
|
||||
) -> Graphic {
|
||||
content.into()
|
||||
}
|
||||
|
||||
/// The transitional level bridge: the wire's records as the legacy list an
|
||||
/// unconverted consumer expects, attributes copied through their erased
|
||||
/// reads. Registered under the legacy convert identifiers; the rows die with
|
||||
/// the last legacy consumer.
|
||||
#[node_macro::node(category(""))]
|
||||
pub fn level_to_list<T: Clone + Send + Sync + CacheHash + 'static>(
|
||||
_: impl Ctx + ExtractIndex + InjectIndex + Copy,
|
||||
#[implementations(Graphic, Vector, Raster<CPU>, Raster<GPU>, Color, GradientStops, String)] value: IList<T>,
|
||||
_converter: (),
|
||||
) -> List<T> {
|
||||
// SAFETY: a materialized input's frames are arena-resident.
|
||||
let item = unsafe { core_types::record::GroupItem::from_resident(value.batch()) };
|
||||
graphic_types::graphic::run_to_render_list::<T>(&item).expect("the run holds the row's element type")
|
||||
}
|
||||
|
||||
pub use _level_to_list_mod::level_to_list_entries;
|
||||
|
||||
/// Removes a level of nesting from a `Graphic[]`, or all nesting if "Fully Flatten" is enabled.
|
||||
#[node_macro::node(category("General"), extent(flatten_graphic_extent))]
|
||||
pub fn flatten_graphic(ctx: impl Ctx + ExtractIndex + InjectIndex + Copy, content: IList<Graphic>, fully_flatten: bool) -> Result<IList<(Graphic, Attr<TransformAttr>)>, Interrupt> {
|
||||
|
||||
@@ -1,30 +1,48 @@
|
||||
use core::f64;
|
||||
use core_types::attribute::{Attr, Transform as TransformAttr};
|
||||
use core_types::color::Color;
|
||||
use core_types::gpoll::Interrupt;
|
||||
use core_types::list::{List, ListDyn};
|
||||
use core_types::extent::{ExtentIn, LevelIn, ValueIn};
|
||||
use core_types::gpoll::{Extent, GPoll, Interrupt};
|
||||
use core_types::transform::{ApplyTransform, ScaleType, Transform};
|
||||
use core_types::{ATTR_TRANSFORM, Context, Ctx, DeriveCtx, InjectFootprint, ModifyFootprint};
|
||||
use core_types::{CacheHash, Context, Ctx, DeriveCtx, ExtractIndex, InjectFootprint, InjectIndex, ModifyFootprint};
|
||||
use glam::{DAffine2, DMat2, DVec2};
|
||||
use graphic_types::Graphic;
|
||||
use graphic_types::Vector;
|
||||
use graphic_types::raster_types::{CPU, GPU, Raster};
|
||||
use vector_types::GradientStops;
|
||||
|
||||
/// Applies the specified transform to the input value, which may be a graphic type or another transform.
|
||||
#[node_macro::node(category("Math: Transform"))]
|
||||
fn transform<T: ApplyTransform + 'static>(
|
||||
/// Applies the specified transform to each lane of the input wire, composing onto the lane's transform attribute.
|
||||
#[node_macro::node(category("Math: Transform"), extent(transform_extent))]
|
||||
fn transform<T>(
|
||||
ctx: impl Ctx + DeriveCtx + ModifyFootprint,
|
||||
#[implementations(
|
||||
Context -> DAffine2,
|
||||
Context -> DVec2,
|
||||
Context -> List<Graphic>,
|
||||
Context -> List<String>,
|
||||
Context -> List<Vector>,
|
||||
Context -> List<Raster<CPU>>,
|
||||
Context -> List<Raster<GPU>>,
|
||||
Context -> List<Color>,
|
||||
Context -> List<GradientStops>,
|
||||
)]
|
||||
content: impl Node<Context<'_>, Output = (T, Attr<TransformAttr>)>,
|
||||
#[widget(ParsedWidgetOverride::Custom = "transform_translation")] translation: DVec2,
|
||||
#[widget(ParsedWidgetOverride::Custom = "transform_rotation")] rotation: f64,
|
||||
#[widget(ParsedWidgetOverride::Custom = "transform_scale")]
|
||||
#[default(1., 1.)]
|
||||
scale: DVec2,
|
||||
#[widget(ParsedWidgetOverride::Custom = "transform_skew")] skew: DVec2,
|
||||
) -> Result<(T, Attr<TransformAttr>), Interrupt> {
|
||||
let trs = DAffine2::from_scale_angle_translation(scale, rotation.to_radians(), translation);
|
||||
let skew = DAffine2::from_cols_array(&[1., skew.y.to_radians().tan(), skew.x.to_radians().tan(), 1., 0., 0.]);
|
||||
let matrix = trs * skew;
|
||||
|
||||
let transformed = ctx.modify_footprint(|footprint| footprint.apply_transform(&matrix));
|
||||
let (element, transform) = content.eval(&transformed.ctx())?;
|
||||
|
||||
Ok((element, Attr(matrix * *transform)))
|
||||
}
|
||||
|
||||
fn transform_extent(content: ExtentIn<'_>, _translation: ValueIn<'_, DVec2>, _rotation: ValueIn<'_, f64>, _scale: ValueIn<'_, DVec2>, _skew: ValueIn<'_, DVec2>, level: LevelIn) -> GPoll<Extent> {
|
||||
content.at(level)
|
||||
}
|
||||
|
||||
/// The transform applied to a plain transform or point value. Registered under the same identifier
|
||||
/// as the leveled `transform`, serving its value-typed rows.
|
||||
#[node_macro::node(category(""))]
|
||||
fn transform_value<T: ApplyTransform + 'static>(
|
||||
ctx: impl Ctx + DeriveCtx + ModifyFootprint,
|
||||
#[implementations(Context -> DAffine2, Context -> DVec2)]
|
||||
content: impl Node<Context<'_>, Output = T>,
|
||||
#[widget(ParsedWidgetOverride::Custom = "transform_translation")] translation: DVec2,
|
||||
#[widget(ParsedWidgetOverride::Custom = "transform_rotation")] rotation: f64,
|
||||
@@ -45,71 +63,55 @@ fn transform<T: ApplyTransform + 'static>(
|
||||
Ok(transform_target)
|
||||
}
|
||||
|
||||
pub use _transform_value_mod::transform_value_entries;
|
||||
|
||||
/// Resets the desired components of the input transform to their default values. If all components are reset, the output will be set to the identity transform.
|
||||
/// Shear is represented jointly by rotation and scale, so resetting both will also remove any shear.
|
||||
#[node_macro::node(category("Math: Transform"))]
|
||||
fn reset_transform<T>(
|
||||
_: impl Ctx,
|
||||
#[implementations(
|
||||
List<Graphic>,
|
||||
List<Vector>,
|
||||
List<Raster<CPU>>,
|
||||
List<Raster<GPU>>,
|
||||
List<Color>,
|
||||
List<GradientStops>,
|
||||
)]
|
||||
mut content: List<T>,
|
||||
(element, transform): (T, Attr<TransformAttr>),
|
||||
#[default(true)] reset_translation: bool,
|
||||
reset_rotation: bool,
|
||||
reset_scale: bool,
|
||||
) -> List<T> {
|
||||
for row_transform in content.iter_attribute_values_mut_or_default::<DAffine2>(ATTR_TRANSFORM) {
|
||||
if reset_translation {
|
||||
row_transform.translation = DVec2::ZERO;
|
||||
}
|
||||
|
||||
match (reset_rotation, reset_scale) {
|
||||
(true, true) => row_transform.matrix2 = DMat2::IDENTITY,
|
||||
(true, false) => {
|
||||
let scale = row_transform.scale_magnitudes();
|
||||
row_transform.matrix2 = DMat2::from_diagonal(scale);
|
||||
}
|
||||
(false, true) => {
|
||||
let rotation = row_transform.decompose_rotation();
|
||||
row_transform.matrix2 = DMat2::from_angle(rotation);
|
||||
}
|
||||
(false, false) => {}
|
||||
}
|
||||
) -> (T, Attr<TransformAttr>) {
|
||||
let mut row_transform = *transform;
|
||||
if reset_translation {
|
||||
row_transform.translation = DVec2::ZERO;
|
||||
}
|
||||
content
|
||||
|
||||
match (reset_rotation, reset_scale) {
|
||||
(true, true) => row_transform.matrix2 = DMat2::IDENTITY,
|
||||
(true, false) => {
|
||||
let scale = row_transform.scale_magnitudes();
|
||||
row_transform.matrix2 = DMat2::from_diagonal(scale);
|
||||
}
|
||||
(false, true) => {
|
||||
let rotation = row_transform.decompose_rotation();
|
||||
row_transform.matrix2 = DMat2::from_angle(rotation);
|
||||
}
|
||||
(false, false) => {}
|
||||
}
|
||||
(element, Attr(row_transform))
|
||||
}
|
||||
|
||||
/// Overwrites the transform of each item in the input `List` with the specified transform.
|
||||
/// Overwrites the transform of each lane of the input wire with the specified transform.
|
||||
#[node_macro::node(category("Math: Transform"))]
|
||||
fn replace_transform<T>(
|
||||
_: impl Ctx + InjectFootprint,
|
||||
#[implementations(
|
||||
List<Graphic>,
|
||||
List<Vector>,
|
||||
List<Raster<CPU>>,
|
||||
List<Raster<GPU>>,
|
||||
List<Color>,
|
||||
List<GradientStops>,
|
||||
)]
|
||||
mut content: List<T>,
|
||||
transform: DAffine2,
|
||||
) -> List<T> {
|
||||
for row_transform in content.iter_attribute_values_mut_or_default::<DAffine2>(ATTR_TRANSFORM) {
|
||||
*row_transform = transform.transform();
|
||||
}
|
||||
content
|
||||
fn replace_transform<T>(_: impl Ctx + InjectFootprint, (element, _content_transform): (T, Attr<TransformAttr>), transform: DAffine2) -> (T, Attr<TransformAttr>) {
|
||||
(element, Attr(transform))
|
||||
}
|
||||
|
||||
// TODO: Figure out how this node should behave once #2982 is implemented.
|
||||
/// Obtains the transform of the first item in the input `List`, if present.
|
||||
/// Obtains the transform of the first lane of the input wire, if present.
|
||||
#[node_macro::node(category("Math: Transform"), path(core_types::vector))]
|
||||
fn extract_transform(_: impl Ctx, content: ListDyn) -> DAffine2 {
|
||||
content.attribute::<DAffine2>(ATTR_TRANSFORM, 0).copied().unwrap_or_default()
|
||||
fn extract_transform<T: Clone + Send + Sync + CacheHash + 'static>(
|
||||
_: impl Ctx + ExtractIndex + InjectIndex + Copy,
|
||||
#[implementations(Graphic, Vector, Raster<CPU>, Raster<GPU>, Color, GradientStops)] content: IList<T>,
|
||||
) -> DAffine2 {
|
||||
match content.len() {
|
||||
0 => DAffine2::default(),
|
||||
_ => content.lane(0).attr::<TransformAttr>(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Produces the inverse of the input transform, which is the transform that undoes the effect of the original transform.
|
||||
|
||||
@@ -8,7 +8,7 @@ use vector_types::vector::VectorModification;
|
||||
|
||||
/// Applies a differential modification to a vector path, associating changes made by the Pen and Path tools to indices of edited points and segments.
|
||||
#[node_macro::node(category(""))]
|
||||
fn path_modify(_ctx: impl Ctx, mut vector: List<Vector>, modification: Box<VectorModification>, node_path: List<NodeId>) -> List<Vector> {
|
||||
fn path_modify(_ctx: impl Ctx, mut vector: List<Vector>, modification: Box<VectorModification>, node_path: Vec<NodeId>) -> List<Vector> {
|
||||
use core_types::list::Item;
|
||||
|
||||
if vector.is_empty() {
|
||||
@@ -23,7 +23,7 @@ fn path_modify(_ctx: impl Ctx, mut vector: List<Vector>, modification: Box<Vecto
|
||||
// matching the `path_of_subgraph` proto so editor tools can route data back to the parent layer.
|
||||
let subgraph_path: List<NodeId> = {
|
||||
let len = node_path.len();
|
||||
node_path.into_iter().take(len.saturating_sub(1)).collect()
|
||||
node_path.into_iter().take(len.saturating_sub(1)).map(Item::new_from_element).collect()
|
||||
};
|
||||
let existing: List<NodeId> = vector.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, 0);
|
||||
vector.set_attribute(ATTR_EDITOR_LAYER_PATH, 0, if existing.is_empty() { subgraph_path } else { existing });
|
||||
|
||||
@@ -8,7 +8,7 @@ use core_types::list::{Item, ItemAttributeValues, List, ListDyn};
|
||||
use core_types::registry::types::{Angle, Length, Multiplier, Percentage, PixelLength, Progression, SeedValue};
|
||||
use core_types::transform::{Footprint, Transform};
|
||||
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, Color, Ctx, DeriveCtx};
|
||||
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 graphic_types::Vector;
|
||||
use graphic_types::graphic::{bake_paint_transforms, graphic_list_at, has_paint_at, is_paint_present, set_paint_attribute_at};
|
||||
@@ -155,8 +155,8 @@ where
|
||||
|
||||
/// Applies a fill style to the vector content, giving an appearance to the area within the interior of the geometry.
|
||||
#[node_macro::node(category("Vector: Style"), path(graphene_core::vector), properties("fill_properties"))]
|
||||
fn fill<V: VectorListIterMut + Send, F: IntoGraphicList + Send + 'static>(
|
||||
_: impl Ctx,
|
||||
fn fill<V: VectorListIterMut + Send, P: Clone + Send + Sync + CacheHash + 'static>(
|
||||
_: impl Ctx + ExtractIndex + InjectIndex + Copy,
|
||||
/// The content with vector paths to apply the fill style to.
|
||||
#[implementations(
|
||||
List<Vector>, List<Vector>, List<Vector>, List<Vector>, List<Vector>, List<Vector>,
|
||||
@@ -166,16 +166,20 @@ fn fill<V: VectorListIterMut + Send, F: IntoGraphicList + Send + 'static>(
|
||||
/// The fill to paint the path with.
|
||||
#[default(Color::BLACK)]
|
||||
#[implementations(
|
||||
List<Graphic>, List<Vector>, List<Color>, List<GradientStops>, List<Raster<CPU>>, List<Raster<GPU>>,
|
||||
List<Graphic>, List<Vector>, List<Color>, List<GradientStops>, List<Raster<CPU>>, List<Raster<GPU>>,
|
||||
Graphic, Vector, Color, GradientStops, Raster<CPU>, Raster<GPU>,
|
||||
Graphic, Vector, Color, GradientStops, Raster<CPU>, Raster<GPU>,
|
||||
)]
|
||||
mut fill: F,
|
||||
_backup_color: List<Color>,
|
||||
_backup_gradient: List<GradientStops>,
|
||||
fill: IList<P>,
|
||||
_backup_color: IList<Color>,
|
||||
_backup_gradient: IList<GradientStops>,
|
||||
_gradient_type: GradientType,
|
||||
_spread_method: GradientSpreadMethod,
|
||||
_transform: Option<DAffine2>,
|
||||
) -> V {
|
||||
) -> V
|
||||
where
|
||||
List<P>: IntoGraphicList,
|
||||
{
|
||||
let mut fill: List<P> = legacy_list_of(fill);
|
||||
if let Some(gradient) = (&mut fill as &mut dyn std::any::Any).downcast_mut::<List<GradientStops>>() {
|
||||
if gradient.iter_attribute_values::<GradientType>(ATTR_GRADIENT_TYPE).is_none() {
|
||||
for value in gradient.iter_attribute_values_mut_or_default::<GradientType>(ATTR_GRADIENT_TYPE) {
|
||||
@@ -250,41 +254,21 @@ impl IntoF64Vec for String {
|
||||
|
||||
/// Applies a stroke style to the vector content, giving an appearance to the area within the outline of the geometry.
|
||||
#[node_macro::node(category("Vector: Style"), path(graphene_core::vector), properties("stroke_properties"))]
|
||||
fn stroke<V, L: IntoF64Vec, P: IntoGraphicList + Send + 'static>(
|
||||
_: impl Ctx,
|
||||
fn stroke<V, P: Clone + Send + Sync + CacheHash + 'static>(
|
||||
_: impl Ctx + ExtractIndex + InjectIndex + Copy,
|
||||
/// The content with vector paths to apply the stroke style to.
|
||||
#[implementations(
|
||||
List<Vector>, List<Vector>, List<Vector>,
|
||||
List<Vector>, List<Vector>, List<Vector>,
|
||||
List<Vector>, List<Vector>, List<Vector>,
|
||||
List<Vector>, List<Vector>, List<Vector>,
|
||||
List<Vector>, List<Vector>, List<Vector>,
|
||||
List<Vector>, List<Vector>, List<Vector>,
|
||||
List<Graphic>, List<Graphic>, List<Graphic>,
|
||||
List<Graphic>, List<Graphic>, List<Graphic>,
|
||||
List<Graphic>, List<Graphic>, List<Graphic>,
|
||||
List<Graphic>, List<Graphic>, List<Graphic>,
|
||||
List<Graphic>, List<Graphic>, List<Graphic>,
|
||||
List<Graphic>, List<Graphic>, List<Graphic>,
|
||||
List<Vector>, List<Vector>, List<Vector>, List<Vector>, List<Vector>, List<Vector>,
|
||||
List<Graphic>, List<Graphic>, List<Graphic>, List<Graphic>, List<Graphic>, List<Graphic>,
|
||||
)]
|
||||
mut content: List<V>,
|
||||
/// The stroke paint.
|
||||
#[default(Color::BLACK)]
|
||||
#[implementations(
|
||||
List<Graphic>, List<Graphic>, List<Graphic>,
|
||||
List<Vector>, List<Vector>, List<Vector>,
|
||||
List<Color>, List<Color>, List<Color>,
|
||||
List<GradientStops>, List<GradientStops>, List<GradientStops>,
|
||||
List<Raster<CPU>>, List<Raster<CPU>>, List<Raster<CPU>>,
|
||||
List<Raster<GPU>>, List<Raster<GPU>>, List<Raster<GPU>>,
|
||||
List<Graphic>, List<Graphic>, List<Graphic>,
|
||||
List<Vector>, List<Vector>, List<Vector>,
|
||||
List<Color>, List<Color>, List<Color>,
|
||||
List<GradientStops>, List<GradientStops>, List<GradientStops>,
|
||||
List<Raster<CPU>>, List<Raster<CPU>>, List<Raster<CPU>>,
|
||||
List<Raster<GPU>>, List<Raster<GPU>>, List<Raster<GPU>>,
|
||||
Graphic, Vector, Color, GradientStops, Raster<CPU>, Raster<GPU>,
|
||||
Graphic, Vector, Color, GradientStops, Raster<CPU>, Raster<GPU>,
|
||||
)]
|
||||
paint: P,
|
||||
paint: IList<P>,
|
||||
/// The stroke thickness.
|
||||
#[unit(" px")]
|
||||
#[default(2.)]
|
||||
@@ -302,29 +286,16 @@ fn stroke<V, L: IntoF64Vec, P: IntoGraphicList + Send + 'static>(
|
||||
/// The order to paint the stroke on top of the fill, or the fill on top of the stroke.
|
||||
paint_order: PaintOrder,
|
||||
/// The stroke dash lengths. Each length forms a distance in a pattern where the first length is a dash, the second is a gap, and so on. If the list is an odd length, the pattern repeats with solid-gap roles reversed.
|
||||
#[implementations(
|
||||
List<f64>, f64, String,
|
||||
List<f64>, f64, String,
|
||||
List<f64>, f64, String,
|
||||
List<f64>, f64, String,
|
||||
List<f64>, f64, String,
|
||||
List<f64>, f64, String,
|
||||
List<f64>, f64, String,
|
||||
List<f64>, f64, String,
|
||||
List<f64>, f64, String,
|
||||
List<f64>, f64, String,
|
||||
List<f64>, f64, String,
|
||||
List<f64>, f64, String,
|
||||
)]
|
||||
dash_lengths: L,
|
||||
dash_lengths: IList<f64>,
|
||||
/// The phase offset distance from the starting point of the dash pattern.
|
||||
#[unit(" px")]
|
||||
dash_offset: f64,
|
||||
) -> List<V>
|
||||
where
|
||||
List<V>: VectorListIterMut + Send,
|
||||
List<P>: IntoGraphicList,
|
||||
{
|
||||
let dash_lengths = dash_lengths.into_vec().into_iter().map(|length| length.max(0.)).collect();
|
||||
let dash_lengths = (0..dash_lengths.len()).map(|index| dash_lengths.get(index).max(0.)).collect();
|
||||
|
||||
let stroke = Stroke {
|
||||
weight,
|
||||
@@ -344,7 +315,7 @@ where
|
||||
vector.stroke = Some(stroke);
|
||||
});
|
||||
|
||||
let paint = paint.into_graphic_list();
|
||||
let paint = legacy_list_of(paint).into_graphic_list();
|
||||
content.for_each_vector_list_mut(|vector_list| {
|
||||
// Broadcast the same paint to every item, scanning the attribute column once instead of per index
|
||||
for slot in vector_list.iter_attribute_values_mut_or_default::<List<Graphic>>(ATTR_STROKE) {
|
||||
@@ -354,6 +325,14 @@ where
|
||||
content
|
||||
}
|
||||
|
||||
/// The transitional value bridge: a materialized level as the legacy list the
|
||||
/// unconverted body consumes.
|
||||
fn legacy_list_of<T: Clone + Send + Sync + 'static>(level: core_types::node::List<'_, T>) -> List<T> {
|
||||
// SAFETY: a materialized input's frames are arena-resident.
|
||||
let item = unsafe { core_types::record::GroupItem::from_resident(level.batch()) };
|
||||
graphic_types::graphic::run_to_render_list::<T>(&item).expect("the run holds the row's element type")
|
||||
}
|
||||
|
||||
#[node_macro::node(name("Copy to Points"), category("Repeat"), path(core_types::vector))]
|
||||
fn copy_to_points<I: Send + Clone>(
|
||||
_: impl Ctx,
|
||||
|
||||
Reference in New Issue
Block a user