Skip to content

Commit 137c24e

Browse files
committed
Filter transform instances
1 parent f184e4a commit 137c24e

File tree

9 files changed

+297
-0
lines changed

9 files changed

+297
-0
lines changed

editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1485,6 +1485,112 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
14851485
description: Cow::Borrowed("TODO"),
14861486
properties: None,
14871487
},
1488+
DocumentNodeDefinition {
1489+
identifier: "Transform2",
1490+
category: "Math: Transform",
1491+
node_template: NodeTemplate {
1492+
document_node: DocumentNode {
1493+
inputs: vec![
1494+
NodeInput::value(TaggedValue::DAffine2(DAffine2::default()), true),
1495+
NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false),
1496+
NodeInput::value(TaggedValue::F64(0.), false),
1497+
NodeInput::value(TaggedValue::DVec2(DVec2::ONE), false),
1498+
NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false),
1499+
NodeInput::value(TaggedValue::IndexOperationFilter((0..=1).into()), false),
1500+
],
1501+
implementation: DocumentNodeImplementation::Network(NodeNetwork {
1502+
exports: vec![NodeInput::node(NodeId(1), 0)],
1503+
nodes: [
1504+
DocumentNode {
1505+
inputs: vec![NodeInput::network(generic!(T), 0)],
1506+
implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
1507+
manual_composition: Some(generic!(T)),
1508+
skip_deduplication: true,
1509+
..Default::default()
1510+
},
1511+
DocumentNode {
1512+
inputs: vec![
1513+
NodeInput::node(NodeId(0), 0),
1514+
NodeInput::network(concrete!(DVec2), 1),
1515+
NodeInput::network(concrete!(f64), 2),
1516+
NodeInput::network(concrete!(DVec2), 3),
1517+
NodeInput::network(concrete!(DVec2), 4),
1518+
NodeInput::network(fn_type!(Context, bool), 5),
1519+
],
1520+
manual_composition: Some(concrete!(Context)),
1521+
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::transform_two::IDENTIFIER),
1522+
..Default::default()
1523+
},
1524+
]
1525+
.into_iter()
1526+
.enumerate()
1527+
.map(|(id, node)| (NodeId(id as u64), node))
1528+
.collect(),
1529+
..Default::default()
1530+
}),
1531+
..Default::default()
1532+
},
1533+
persistent_node_metadata: DocumentNodePersistentMetadata {
1534+
network_metadata: Some(NodeNetworkMetadata {
1535+
persistent_metadata: NodeNetworkPersistentMetadata {
1536+
node_metadata: [
1537+
DocumentNodeMetadata {
1538+
persistent_metadata: DocumentNodePersistentMetadata {
1539+
display_name: "Monitor".to_string(),
1540+
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),
1541+
..Default::default()
1542+
},
1543+
..Default::default()
1544+
},
1545+
DocumentNodeMetadata {
1546+
persistent_metadata: DocumentNodePersistentMetadata {
1547+
display_name: "Transform".to_string(),
1548+
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 0)),
1549+
..Default::default()
1550+
},
1551+
..Default::default()
1552+
},
1553+
]
1554+
.into_iter()
1555+
.enumerate()
1556+
.map(|(id, node)| (NodeId(id as u64), node))
1557+
.collect(),
1558+
..Default::default()
1559+
},
1560+
..Default::default()
1561+
}),
1562+
input_metadata: vec![
1563+
("Value", "TODO").into(),
1564+
InputMetadata::with_name_description_override(
1565+
"Translation",
1566+
"TODO",
1567+
WidgetOverride::Vec2(Vec2InputSettings {
1568+
x: "X".to_string(),
1569+
y: "Y".to_string(),
1570+
unit: " px".to_string(),
1571+
..Default::default()
1572+
}),
1573+
),
1574+
InputMetadata::with_name_description_override("Rotation", "TODO", WidgetOverride::Custom("transform_rotation".to_string())),
1575+
InputMetadata::with_name_description_override(
1576+
"Scale",
1577+
"TODO",
1578+
WidgetOverride::Vec2(Vec2InputSettings {
1579+
x: "W".to_string(),
1580+
y: "H".to_string(),
1581+
unit: "x".to_string(),
1582+
..Default::default()
1583+
}),
1584+
),
1585+
InputMetadata::with_name_description_override("Skew", "TODO", WidgetOverride::Custom("transform_skew".to_string())),
1586+
],
1587+
output_names: vec!["Data".to_string()],
1588+
..Default::default()
1589+
},
1590+
},
1591+
description: Cow::Borrowed("TODO"),
1592+
properties: None,
1593+
},
14881594
DocumentNodeDefinition {
14891595
identifier: "Boolean Operation",
14901596
category: "Vector",

editor/src/messages/portfolio/document/node_graph/node_properties.rs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ use graphene_std::raster::{
2020
SelectiveColorChoice,
2121
};
2222
use graphene_std::raster_types::{CPU, GPU, RasterDataTable};
23+
use graphene_std::selection::IndexOperationFilter;
2324
use graphene_std::text::Font;
2425
use graphene_std::transform::{Footprint, ReferencePoint, Transform};
2526
use graphene_std::vector::VectorDataTable;
@@ -182,6 +183,7 @@ pub(crate) fn property_from_type(
182183
// ==========================
183184
Some(x) if x == TypeId::of::<Vec<f64>>() => array_of_number_widget(default_info, TextInput::default()).into(),
184185
Some(x) if x == TypeId::of::<Vec<DVec2>>() => array_of_coordinates_widget(default_info, TextInput::default()).into(),
186+
Some(x) if x == TypeId::of::<IndexOperationFilter>() => array_of_ranges(default_info, TextInput::default()).into(),
185187
// ====================
186188
// GRAPHICAL DATA TYPES
187189
// ====================
@@ -754,6 +756,72 @@ pub fn array_of_coordinates_widget(parameter_widgets_info: ParameterWidgetsInfo,
754756
widgets
755757
}
756758

759+
pub fn array_of_ranges(parameter_widgets_info: ParameterWidgetsInfo, text_props: TextInput) -> Vec<WidgetHolder> {
760+
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
761+
762+
let mut widgets = start_widgets(parameter_widgets_info);
763+
764+
let from_string = |string: &str| {
765+
let mut result = Vec::new();
766+
let mut start: Option<usize> = None;
767+
let mut number: Option<usize> = None;
768+
let mut seen_continue = false;
769+
for c in string.chars() {
770+
if let Some(digit) = c.to_digit(10) {
771+
if !seen_continue {
772+
if let Some(start) = start.take() {
773+
result.push(start..=start);
774+
}
775+
}
776+
let mut value = number.unwrap_or_default();
777+
value *= 10;
778+
value += digit as usize;
779+
number = Some(value);
780+
} else {
781+
if let Some(number) = number.take() {
782+
if let Some(start) = start.take() {
783+
result.push(start.min(number)..=start.max(number));
784+
} else {
785+
start = Some(number);
786+
}
787+
seen_continue = false;
788+
}
789+
if c == '=' || c == '-' || c == '.' {
790+
seen_continue = true;
791+
}
792+
}
793+
}
794+
if let Some(number) = number.take() {
795+
if let Some(start) = start.take() {
796+
result.push(start.min(number)..=start.max(number));
797+
} else {
798+
result.push(number..=number);
799+
}
800+
}
801+
if let Some(start) = start.take() {
802+
result.push(start..=start);
803+
}
804+
805+
Some(TaggedValue::IndexOperationFilter(result.into()))
806+
};
807+
808+
let Some(document_node) = document_node else { return Vec::new() };
809+
let Some(input) = document_node.inputs.get(index) else {
810+
log::warn!("A widget failed to be built because its node's input index is invalid.");
811+
return vec![];
812+
};
813+
if let Some(TaggedValue::IndexOperationFilter(x)) = &input.as_non_exposed_value() {
814+
widgets.extend_from_slice(&[
815+
Separator::new(SeparatorType::Unrelated).widget_holder(),
816+
text_props
817+
.value(x.to_string())
818+
.on_update(optionally_update_value(move |x: &TextInput| from_string(&x.value), node_id, index))
819+
.widget_holder(),
820+
])
821+
}
822+
widgets
823+
}
824+
757825
pub fn font_inputs(parameter_widgets_info: ParameterWidgetsInfo) -> (Vec<WidgetHolder>, Option<Vec<WidgetHolder>>) {
758826
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
759827

editor/src/node_graph_executor/runtime.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,7 @@ impl NodeRuntime {
228228

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

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

node-graph/gcore/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ pub mod raster;
2323
pub mod raster_types;
2424
pub mod registry;
2525
pub mod render_complexity;
26+
pub mod selection;
2627
pub mod structural;
2728
pub mod text;
2829
pub mod transform;

node-graph/gcore/src/selection.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
use crate::{Ctx, ExtractIndex};
2+
3+
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Hash, dyn_any::DynAny, Default)]
4+
pub struct IndexOperationFilter {
5+
values: Vec<core::ops::RangeInclusive<usize>>,
6+
}
7+
8+
impl IndexOperationFilter {
9+
pub fn contains(&self, index: usize) -> bool {
10+
self.values.iter().any(|range| range.contains(&index))
11+
}
12+
}
13+
14+
impl From<Vec<core::ops::RangeInclusive<usize>>> for IndexOperationFilter {
15+
fn from(values: Vec<core::ops::RangeInclusive<usize>>) -> Self {
16+
Self { values }
17+
}
18+
}
19+
20+
impl From<core::ops::RangeInclusive<usize>> for IndexOperationFilter {
21+
fn from(value: core::ops::RangeInclusive<usize>) -> Self {
22+
Self { values: vec![value] }
23+
}
24+
}
25+
26+
impl core::fmt::Display for IndexOperationFilter {
27+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28+
let mut started = false;
29+
for value in &self.values {
30+
if started {
31+
write!(f, ", ")?;
32+
}
33+
started = true;
34+
if value.start() == value.end() {
35+
write!(f, "{}", value.start())?;
36+
} else {
37+
write!(f, "{}..={}", value.start(), value.end())?;
38+
}
39+
}
40+
Ok(())
41+
}
42+
}
43+
44+
#[node_macro::node(category("Filtering"), path(graphene_core::vector))]
45+
async fn evaluate_index_operation_filter(ctx: impl Ctx + ExtractIndex, filter: IndexOperationFilter) -> bool {
46+
let index = ctx.try_index().and_then(|indexes| indexes.last().copied()).unwrap_or_default();
47+
filter.contains(index)
48+
}

node-graph/gcore/src/transform_nodes.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,44 @@ use crate::{CloneVarArgs, Context, Ctx, ExtractAll, GraphicGroupTable, OwnedCont
66
use core::f64;
77
use glam::{DAffine2, DVec2};
88

9+
#[node_macro::node(category(""))]
10+
async fn transform_two(
11+
ctx: impl Ctx + CloneVarArgs + ExtractAll,
12+
value: impl Node<Context<'static>, Output = VectorDataTable>,
13+
translate: DVec2,
14+
rotate: f64,
15+
scale: DVec2,
16+
skew: DVec2,
17+
selection: impl Node<Context<'static>, Output = bool>,
18+
) -> VectorDataTable {
19+
let matrix = DAffine2::from_scale_angle_translation(scale, rotate, translate) * DAffine2::from_cols_array(&[1., skew.y, skew.x, 1., 0., 0.]);
20+
21+
let footprint = ctx.try_footprint().copied();
22+
23+
let mut transform_target = {
24+
let mut new_ctx = OwnedContextImpl::from(ctx.clone());
25+
if let Some(mut footprint) = footprint {
26+
footprint.apply_transform(&matrix);
27+
new_ctx = new_ctx.with_footprint(footprint);
28+
}
29+
value.eval(new_ctx.into_context()).await
30+
};
31+
32+
for (index, instance) in transform_target.instance_mut_iter().enumerate() {
33+
let new_ctx = OwnedContextImpl::from(ctx.clone()).with_index(index);
34+
35+
let should_eval = selection.eval(new_ctx.into_context()).await;
36+
if should_eval {
37+
info!("Applying to {index}");
38+
*instance.transform = matrix * *instance.transform;
39+
} else {
40+
info!("Skipping index {index}");
41+
}
42+
}
43+
44+
transform_target
45+
}
46+
947
#[node_macro::node(category(""))]
1048
async fn transform<T: ApplyTransform + 'n + 'static>(
1149
ctx: impl Ctx + CloneVarArgs + ExtractAll,

node-graph/graph-craft/src/document/value.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use graphene_application_io::SurfaceFrame;
88
use graphene_brush::brush_cache::BrushCache;
99
use graphene_brush::brush_stroke::BrushStroke;
1010
use graphene_core::raster_types::CPU;
11+
use graphene_core::selection::IndexOperationFilter;
1112
use graphene_core::transform::ReferencePoint;
1213
use graphene_core::uuid::NodeId;
1314
use graphene_core::vector::style::Fill;
@@ -247,6 +248,8 @@ tagged_value! {
247248
ReferencePoint(graphene_core::transform::ReferencePoint),
248249
CentroidType(graphene_core::vector::misc::CentroidType),
249250
BooleanOperation(graphene_path_bool::BooleanOperation),
251+
252+
IndexOperationFilter(IndexOperationFilter),
250253
}
251254

252255
impl TaggedValue {

node-graph/graphene-cli/src/main.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,8 @@ fn compile_graph(document_string: String, editor_api: Arc<WasmEditorApi>) -> Res
187187
let substitutions = preprocessor::generate_node_substitutions();
188188
preprocessor::expand_network(&mut network, &substitutions);
189189

190+
preprocessor::evaluate_index_operation_filter(&mut network);
191+
190192
let wrapped_network = wrap_network_in_scope(network.clone(), editor_api);
191193

192194
let compiler = Compiler {};

node-graph/preprocessor/src/lib.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,36 @@ pub fn expand_network(network: &mut NodeNetwork, substitutions: &HashMap<ProtoNo
2424
}
2525
}
2626

27+
/// Referencing the TaggedValue::IndexOperationFilter will expand to a node that contains the evaluate_index_operation_filter
28+
pub fn evaluate_index_operation_filter(network: &mut NodeNetwork) {
29+
let mut new_nodes = Vec::new();
30+
for node in network.nodes.values_mut() {
31+
for input in &mut node.inputs {
32+
if !matches!(input.as_value(), Some(TaggedValue::IndexOperationFilter(_)),) {
33+
continue;
34+
}
35+
let node_id = NodeId::new();
36+
let range_input = std::mem::replace(input, NodeInput::node(node_id, 0));
37+
new_nodes.push((node_id, range_input));
38+
}
39+
match &mut node.implementation {
40+
DocumentNodeImplementation::Network(node_network) => evaluate_index_operation_filter(node_network),
41+
_ => {}
42+
}
43+
}
44+
for (id, range_input) in new_nodes {
45+
network.nodes.insert(
46+
id,
47+
DocumentNode {
48+
inputs: vec![range_input],
49+
manual_composition: Some(concrete!(Context)),
50+
implementation: DocumentNodeImplementation::ProtoNode(graphene_core::selection::evaluate_index_operation_filter::IDENTIFIER),
51+
..Default::default()
52+
},
53+
);
54+
}
55+
}
56+
2757
pub fn generate_node_substitutions() -> HashMap<ProtoNodeIdentifier, DocumentNode> {
2858
let mut custom = HashMap::new();
2959
let node_registry = graphene_core::registry::NODE_REGISTRY.lock().unwrap();

0 commit comments

Comments
 (0)