Filter transform instances with 'Transform Selection' node

This commit is contained in:
hypercube
2025-07-25 01:28:09 +01:00
committed by Keavon Chambers
parent 7cb42b9523
commit 0508da13b9
10 changed files with 357 additions and 2 deletions

View File

@@ -21,6 +21,7 @@ pub mod raster;
pub mod raster_types;
pub mod registry;
pub mod render_complexity;
pub mod selection;
pub mod structural;
pub mod table;
pub mod text;

View File

@@ -0,0 +1,61 @@
use crate::{Ctx, ExtractIndex};
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Hash, dyn_any::DynAny, Default)]
pub enum IndexOperationFilter {
Range(Vec<core::ops::RangeInclusive<usize>>),
#[default]
All,
}
impl IndexOperationFilter {
pub fn contains(&self, index: usize) -> bool {
match self {
Self::Range(range) => range.iter().any(|range| range.contains(&index)),
Self::All => true,
}
}
}
impl From<Vec<core::ops::RangeInclusive<usize>>> for IndexOperationFilter {
fn from(values: Vec<core::ops::RangeInclusive<usize>>) -> Self {
Self::Range(values)
}
}
impl From<core::ops::RangeInclusive<usize>> for IndexOperationFilter {
fn from(value: core::ops::RangeInclusive<usize>) -> Self {
Self::Range(vec![value])
}
}
impl core::fmt::Display for IndexOperationFilter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::All => {
write!(f, "*")?;
}
Self::Range(range) => {
let mut started = false;
for value in range {
if started {
write!(f, ", ")?;
}
started = true;
if value.start() == value.end() {
write!(f, "{}", value.start())?;
} else {
write!(f, "{}..={}", value.start(), value.end())?;
}
}
}
}
Ok(())
}
}
#[node_macro::node(category("Filtering"), path(graphene_core::vector))]
async fn evaluate_index_operation_filter(ctx: impl Ctx + ExtractIndex, filter: IndexOperationFilter) -> bool {
let index = ctx.try_index().and_then(|indexes| indexes.last().copied()).unwrap_or_default();
filter.contains(index)
}

View File

@@ -1,11 +1,89 @@
use crate::raster_types::{CPU, GPU, Raster};
use crate::table::Table;
use crate::transform::{ApplyTransform, Footprint, Transform};
use crate::transform::{ApplyTransform, Footprint, Transform, TransformMut};
use crate::vector::Vector;
use crate::{CloneVarArgs, Context, Ctx, ExtractAll, Graphic, OwnedContextImpl};
use core::f64;
use glam::{DAffine2, DVec2};
/// An updated version of the transform node supporting selecting which instances/rows are transformed
#[node_macro::node(category(""))]
async fn transform_two<T: ApplyTransform2>(
ctx: impl Ctx + CloneVarArgs + ExtractAll,
#[implementations(
Context -> DAffine2,
Context -> DVec2,
Context -> Table<Graphic>,
Context -> Table<Vector>,
Context -> Table<Raster<CPU>>,
Context -> Table<Raster<GPU>>,
)]
value: impl Node<Context<'static>, Output = T>,
translate: DVec2,
rotate: f64,
scale: DVec2,
skew: DVec2,
selection: impl Node<Context<'static>, Output = bool>,
) -> T {
let matrix = DAffine2::from_scale_angle_translation(scale, rotate, translate) * DAffine2::from_cols_array(&[1., skew.y, skew.x, 1., 0., 0.]);
let footprint = ctx.try_footprint().copied();
let mut transform_target = {
let mut new_ctx = OwnedContextImpl::from(ctx.clone());
if let Some(mut footprint) = footprint {
footprint.apply_transform(&matrix);
new_ctx = new_ctx.with_footprint(footprint);
}
value.eval(new_ctx.into_context()).await
};
transform_target.apply_transformation(matrix, &ctx, selection).await;
transform_target
}
/// A trait facilitating applying transforms with a particular selection field.
trait ApplyTransform2 {
async fn apply_transformation<'n>(&mut self, matrix: DAffine2, ctx: &(impl Ctx + ExtractAll + CloneVarArgs), selection: &'n impl crate::Node<'n, Context<'n>, Output = impl Future<Output = bool>>);
}
/// Implementations of applying transforms for a table that implement the filtering based on the selection field.
impl<T> ApplyTransform2 for Table<T> {
async fn apply_transformation<'n>(
&mut self,
matrix: DAffine2,
ctx: &(impl Ctx + ExtractAll + CloneVarArgs),
selection: &'n impl crate::Node<'n, Context<'n>, Output = impl Future<Output = bool>>,
) {
for (index, row) in self.iter_mut().enumerate() {
let new_ctx = OwnedContextImpl::from(ctx.clone()).with_index(index);
let should_eval = selection.eval(new_ctx.into_context()).await;
if should_eval {
info!("Applying to {index}");
*row.transform = matrix * *row.transform;
} else {
info!("Skipping index {index}");
}
}
}
}
/// An implementation for a non-table which ignores the selection
impl<T: TransformMut> ApplyTransform2 for T {
async fn apply_transformation<'n>(&mut self, matrix: DAffine2, _: &(impl Ctx + ExtractAll + CloneVarArgs), _: &'n impl crate::Node<'n, Context<'n>, Output = impl Future<Output = bool>>) {
*self.transform_mut() = matrix * self.transform();
}
}
/// An implementation for a point which ignores the selection
impl ApplyTransform2 for DVec2 {
async fn apply_transformation<'n>(&mut self, matrix: DAffine2, _: &(impl Ctx + ExtractAll + CloneVarArgs), _: &'n impl crate::Node<'n, Context<'n>, Output = impl Future<Output = bool>>) {
*self = matrix.transform_point2(*self);
}
}
#[node_macro::node(category(""))]
async fn transform<T: ApplyTransform + 'n + 'static>(
ctx: impl Ctx + CloneVarArgs + ExtractAll,