Skip to content

Filter transform instances #2935

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1487,6 +1487,113 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
description: Cow::Borrowed("TODO"),
properties: None,
},
// A modified version of the transform node that filters values based on a selection field
DocumentNodeDefinition {
identifier: "Transform Selection",
category: "Math: Transform",
node_template: NodeTemplate {
document_node: DocumentNode {
inputs: vec![
NodeInput::value(TaggedValue::DAffine2(DAffine2::default()), true),
NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false),
NodeInput::value(TaggedValue::F64(0.), false),
NodeInput::value(TaggedValue::DVec2(DVec2::ONE), false),
NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false),
NodeInput::value(TaggedValue::IndexOperationFilter((0..=1).into()), false),
],
implementation: DocumentNodeImplementation::Network(NodeNetwork {
exports: vec![NodeInput::node(NodeId(1), 0)],
nodes: [
DocumentNode {
inputs: vec![NodeInput::network(generic!(T), 0)],
implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
manual_composition: Some(generic!(T)),
skip_deduplication: true,
..Default::default()
},
DocumentNode {
inputs: vec![
NodeInput::node(NodeId(0), 0),
NodeInput::network(concrete!(DVec2), 1),
NodeInput::network(concrete!(f64), 2),
NodeInput::network(concrete!(DVec2), 3),
NodeInput::network(concrete!(DVec2), 4),
NodeInput::network(fn_type!(Context, bool), 5),
],
manual_composition: Some(concrete!(Context)),
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::transform_two::IDENTIFIER),
..Default::default()
},
]
.into_iter()
.enumerate()
.map(|(id, node)| (NodeId(id as u64), node))
.collect(),
..Default::default()
}),
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
network_metadata: Some(NodeNetworkMetadata {
persistent_metadata: NodeNetworkPersistentMetadata {
node_metadata: [
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Monitor".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),
..Default::default()
},
..Default::default()
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Transform".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 0)),
..Default::default()
},
..Default::default()
},
]
.into_iter()
.enumerate()
.map(|(id, node)| (NodeId(id as u64), node))
.collect(),
..Default::default()
},
..Default::default()
}),
input_metadata: vec![
("Value", "TODO").into(),
InputMetadata::with_name_description_override(
"Translation",
"TODO",
WidgetOverride::Vec2(Vec2InputSettings {
x: "X".to_string(),
y: "Y".to_string(),
unit: " px".to_string(),
..Default::default()
}),
),
InputMetadata::with_name_description_override("Rotation", "TODO", WidgetOverride::Custom("transform_rotation".to_string())),
InputMetadata::with_name_description_override(
"Scale",
"TODO",
WidgetOverride::Vec2(Vec2InputSettings {
x: "W".to_string(),
y: "H".to_string(),
unit: "x".to_string(),
..Default::default()
}),
),
InputMetadata::with_name_description_override("Skew", "TODO", WidgetOverride::Custom("transform_skew".to_string())),
],
output_names: vec!["Data".to_string()],
..Default::default()
},
},
description: Cow::Borrowed("Transforms only selected instances based on a selection field"),
properties: None,
},
DocumentNodeDefinition {
identifier: "Boolean Operation",
category: "Vector",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use graphene_std::raster::{
BlendMode, CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, LuminanceCalculation, NoiseType, RedGreenBlue, RedGreenBlueAlpha, RelativeAbsolute,
SelectiveColorChoice,
};
use graphene_std::selection::IndexOperationFilter;
use graphene_std::text::{Font, TextAlign};
use graphene_std::transform::{Footprint, ReferencePoint, Transform};
use graphene_std::vector::misc::{ArcType, CentroidType, GridType, MergeByDistanceAlgorithm, PointSpacingType};
Expand Down Expand Up @@ -177,6 +178,7 @@ pub(crate) fn property_from_type(
// ==========================
Some(x) if x == TypeId::of::<Vec<f64>>() => array_of_number_widget(default_info, TextInput::default()).into(),
Some(x) if x == TypeId::of::<Vec<DVec2>>() => array_of_vec2_widget(default_info, TextInput::default()).into(),
Some(x) if x == TypeId::of::<IndexOperationFilter>() => array_of_ranges(default_info, TextInput::default()).into(),
// ============
// STRUCT TYPES
// ============
Expand Down Expand Up @@ -748,6 +750,77 @@ pub fn array_of_vec2_widget(parameter_widgets_info: ParameterWidgetsInfo, text_p
widgets
}

pub fn array_of_ranges(parameter_widgets_info: ParameterWidgetsInfo, text_props: TextInput) -> Vec<WidgetHolder> {
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;

let mut widgets = start_widgets(parameter_widgets_info);

let from_string = |string: &str| {
let mut result = Vec::new();
let mut start: Option<usize> = None;
let mut number: Option<usize> = None;
let mut seen_continue = false;
for c in string.chars() {
// any string containing a '*' gets all
if c == '*' {
return Some(TaggedValue::IndexOperationFilter(IndexOperationFilter::All));
}

if let Some(digit) = c.to_digit(10) {
if !seen_continue {
if let Some(start) = start.take() {
result.push(start..=start);
}
}
let mut value = number.unwrap_or_default();
value *= 10;
value += digit as usize;
number = Some(value);
} else {
if let Some(number) = number.take() {
if let Some(start) = start.take() {
result.push(start.min(number)..=start.max(number));
} else {
start = Some(number);
}
seen_continue = false;
}
if c == '=' || c == '-' || c == '.' {
seen_continue = true;
}
}
}
if let Some(number) = number.take() {
if let Some(start) = start.take() {
result.push(start.min(number)..=start.max(number));
} else {
result.push(number..=number);
}
}
if let Some(start) = start.take() {
result.push(start..=start);
}

Some(TaggedValue::IndexOperationFilter(result.into()))
};

let Some(document_node) = document_node else { return Vec::new() };
let Some(input) = document_node.inputs.get(index) else {
log::warn!("A widget failed to be built because its node's input index is invalid.");
return vec![];
};
if let Some(TaggedValue::IndexOperationFilter(x)) = &input.as_non_exposed_value() {
widgets.extend_from_slice(&[
Separator::new(SeparatorType::Unrelated).widget_holder(),
text_props
.value(x.to_string())
.on_update(optionally_update_value(move |x: &TextInput| from_string(&x.value), node_id, index))
.widget_holder(),
])
}
widgets
}

pub fn font_inputs(parameter_widgets_info: ParameterWidgetsInfo) -> (Vec<WidgetHolder>, Option<Vec<WidgetHolder>>) {
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;

Expand Down
1 change: 1 addition & 0 deletions editor/src/node_graph_executor/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ impl NodeRuntime {

async fn update_network(&mut self, mut graph: NodeNetwork) -> Result<ResolvedDocumentNodeTypesDelta, String> {
preprocessor::expand_network(&mut graph, &self.substitutions);
preprocessor::evaluate_index_operation_filter(&mut graph);

let scoped_network = wrap_network_in_scope(graph, self.editor_api.clone());

Expand Down
2 changes: 1 addition & 1 deletion frontend/wasm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ wasm-opt = ["-Os", "-g"]
[package.metadata.wasm-pack.profile.profiling.wasm-bindgen]
debug-js-glue = true
demangle-name-section = true
dwarf-debug-info = true
dwarf-debug-info = false

[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = [
Expand Down
1 change: 1 addition & 0 deletions node-graph/gcore/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
61 changes: 61 additions & 0 deletions node-graph/gcore/src/selection.rs
Original file line number Diff line number Diff line change
@@ -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)
}
80 changes: 79 additions & 1 deletion node-graph/gcore/src/transform_nodes.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
Loading
Loading