mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-07-08 00:05:00 +00:00
Rename 'Sample Points' node to 'Sample Polyline' and add a parameter spacing based on separation or quantity (#2727)
* Added Count point Radio button to property pannel * Implemented on Count radio button functionality * Fixed linting and Title case problem * Fixing more linting problem * Instance tables refactor part 8: Make repeater nodes use pivot not bbox and output instance type not group; rename 'Flatten Vector Elements' to 'Flatten Path' and add 'Flatten Vector' (#2697) Make repeater nodes use pivot not bbox and output instance type not group; rename 'Flatten Vector Elements' to 'Flatten Path' and add 'Flatten Vector' * Refactor the 'Bounding Box' node to use Kurbo instead of Bezier-rs (#2662) * use kurbo's default accuracy constant * fix append_bezpath() method * refactor bounding box node * fix append bezpath implementation. * comments --------- Co-authored-by: Keavon Chambers <keavon@keavon.com> * Add overlays for free-floating anchors on hovered/selected vector layers (#2630) * Add selection overlay for free-floating anchors * Add hover overlay for free-floating anchors * Refactor outline_free_floating anchor * Add single-anchor click targets on VectorData * Modify ClickTarget to adapt for Subpath and PointGroup * Fix Rust formatting * Remove debug statements * Add point groups support in VectorDataTable::add_upstream_click_targets * Improve overlay for free floating anchors * Remove datatype for nodes_to_shift * Fix formatting in select_tool.rs * Lints * Code review * Remove references to point_group * Refactor ManipulatorGroup for FreePoint in ClickTargetGroup * Rename ClickTargetGroup to ClickTargetType * Refactor outline_free_floating_anchors into outline * Adapt TransformCage to disable dragging and rotating on a single anchor layer * Fix hover on single points * Fix comments * Lints * Code review pass --------- Co-authored-by: Keavon Chambers <keavon@keavon.com> * Add anchor sliding along adjacent segments in the Path tool (#2682) * Improved comments * Add point sliding with approximate t value * Add similarity calculation * Numerical approach to fit the curve * Reliable point sliding for cubic segments * Fix formatting and clean comments * Fix cubic with one handle logic * Cancel on right click and escape * Two parameter optimization * Esc/ Right click cancellation * Code review * Fix dynamic hints * Revert selected_points_counts and fix comments * Code review --------- Co-authored-by: Keavon Chambers <keavon@keavon.com> * Fix Sample Points node to avoid duplicating endpoints instead of closing its sampled paths (#2714) * Skip duplicate endpoint and close sampled paths in Sample Points node Closes #2713 * Comment --------- Co-authored-by: Keavon Chambers <keavon@keavon.com> * Implemented on Count radio button functionality * Fixed linting and Title case problem * The sample count can now work with adaptive spacing * Readying for production * Rename to 'Sample Polyline' and add migration * Upgrade demo artwork * Add monomorphization --------- Co-authored-by: Keavon Chambers <keavon@keavon.com> Co-authored-by: Priyanshu <indierusty@gmail.com> Co-authored-by: seam0s <153828136+seam0s-dev@users.noreply.github.com> Co-authored-by: Adesh Gupta <148623820+4adex@users.noreply.github.com> Co-authored-by: Ezbaze <68749104+Ezbaze@users.noreply.github.com>
This commit is contained in:
parent
4a65ad290c
commit
504af4e68d
12 changed files with 206 additions and 63 deletions
2
demo-artwork/parametric-dunescape.graphite
generated
2
demo-artwork/parametric-dunescape.graphite
generated
File diff suppressed because one or more lines are too long
2
demo-artwork/procedural-string-lights.graphite
generated
2
demo-artwork/procedural-string-lights.graphite
generated
File diff suppressed because one or more lines are too long
2
demo-artwork/red-dress.graphite
generated
2
demo-artwork/red-dress.graphite
generated
File diff suppressed because one or more lines are too long
|
@ -1822,12 +1822,12 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||||
properties: None,
|
properties: None,
|
||||||
},
|
},
|
||||||
DocumentNodeDefinition {
|
DocumentNodeDefinition {
|
||||||
identifier: "Sample Points",
|
identifier: "Sample Polyline",
|
||||||
category: "Vector: Modifier",
|
category: "Vector: Modifier",
|
||||||
node_template: NodeTemplate {
|
node_template: NodeTemplate {
|
||||||
document_node: DocumentNode {
|
document_node: DocumentNode {
|
||||||
implementation: DocumentNodeImplementation::Network(NodeNetwork {
|
implementation: DocumentNodeImplementation::Network(NodeNetwork {
|
||||||
exports: vec![NodeInput::node(NodeId(4), 0)], // Taken from output 0 of Sample Points
|
exports: vec![NodeInput::node(NodeId(4), 0)],
|
||||||
nodes: [
|
nodes: [
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![NodeInput::network(concrete!(graphene_std::vector::VectorDataTable), 0)],
|
inputs: vec![NodeInput::network(concrete!(graphene_std::vector::VectorDataTable), 0)],
|
||||||
|
@ -1838,13 +1838,15 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![
|
inputs: vec![
|
||||||
NodeInput::network(concrete!(graphene_std::vector::VectorDataTable), 0),
|
NodeInput::network(concrete!(graphene_std::vector::VectorDataTable), 0),
|
||||||
NodeInput::network(concrete!(f64), 1), // From the document node's parameters
|
NodeInput::network(concrete!(vector::misc::PointSpacingType), 1),
|
||||||
NodeInput::network(concrete!(f64), 2), // From the document node's parameters
|
NodeInput::network(concrete!(f64), 2),
|
||||||
NodeInput::network(concrete!(f64), 3), // From the document node's parameters
|
NodeInput::network(concrete!(f64), 3),
|
||||||
NodeInput::network(concrete!(bool), 4), // From the document node's parameters
|
NodeInput::network(concrete!(f64), 4),
|
||||||
NodeInput::node(NodeId(0), 0), // From output 0 of SubpathSegmentLengthsNode
|
NodeInput::network(concrete!(f64), 5),
|
||||||
|
NodeInput::network(concrete!(bool), 6),
|
||||||
|
NodeInput::node(NodeId(0), 0),
|
||||||
],
|
],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::vector::SamplePointsNode")),
|
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::vector::SamplePolylineNode")),
|
||||||
manual_composition: Some(generic!(T)),
|
manual_composition: Some(generic!(T)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
|
@ -1875,6 +1877,8 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||||
}),
|
}),
|
||||||
inputs: vec![
|
inputs: vec![
|
||||||
NodeInput::value(TaggedValue::VectorData(graphene_std::vector::VectorDataTable::default()), true),
|
NodeInput::value(TaggedValue::VectorData(graphene_std::vector::VectorDataTable::default()), true),
|
||||||
|
NodeInput::value(TaggedValue::PointSpacingType(Default::default()), false),
|
||||||
|
NodeInput::value(TaggedValue::F64(100.), false),
|
||||||
NodeInput::value(TaggedValue::F64(100.), false),
|
NodeInput::value(TaggedValue::F64(100.), false),
|
||||||
NodeInput::value(TaggedValue::F64(0.), false),
|
NodeInput::value(TaggedValue::F64(0.), false),
|
||||||
NodeInput::value(TaggedValue::F64(0.), false),
|
NodeInput::value(TaggedValue::F64(0.), false),
|
||||||
|
@ -1889,14 +1893,14 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||||
DocumentNodeMetadata {
|
DocumentNodeMetadata {
|
||||||
persistent_metadata: DocumentNodePersistentMetadata {
|
persistent_metadata: DocumentNodePersistentMetadata {
|
||||||
display_name: "Subpath Segment Lengths".to_string(),
|
display_name: "Subpath Segment Lengths".to_string(),
|
||||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 5)),
|
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 7)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
DocumentNodeMetadata {
|
DocumentNodeMetadata {
|
||||||
persistent_metadata: DocumentNodePersistentMetadata {
|
persistent_metadata: DocumentNodePersistentMetadata {
|
||||||
display_name: "Sample Points".to_string(),
|
display_name: "Sample Polyline".to_string(),
|
||||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 0)),
|
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 0)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
|
@ -1937,18 +1941,28 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||||
}),
|
}),
|
||||||
input_properties: vec![
|
input_properties: vec![
|
||||||
("Vector Data", "The shape to be resampled and converted into a polyline.").into(),
|
("Vector Data", "The shape to be resampled and converted into a polyline.").into(),
|
||||||
|
Into::<PropertiesRow>::into(("Spacing", node_properties::SAMPLE_POLYLINE_TOOLTIP_SPACING)),
|
||||||
PropertiesRow::with_override(
|
PropertiesRow::with_override(
|
||||||
"Spacing",
|
"Separation",
|
||||||
"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).",
|
node_properties::SAMPLE_POLYLINE_TOOLTIP_SEPARATION,
|
||||||
WidgetOverride::Number(NumberInputSettings {
|
WidgetOverride::Number(NumberInputSettings {
|
||||||
min: Some(1.),
|
min: Some(0.),
|
||||||
unit: Some(" px".to_string()),
|
unit: Some(" px".to_string()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
PropertiesRow::with_override(
|
||||||
|
"Quantity",
|
||||||
|
node_properties::SAMPLE_POLYLINE_TOOLTIP_QUANTITY,
|
||||||
|
WidgetOverride::Number(NumberInputSettings {
|
||||||
|
min: Some(2.),
|
||||||
|
is_integer: true,
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
),
|
||||||
PropertiesRow::with_override(
|
PropertiesRow::with_override(
|
||||||
"Start Offset",
|
"Start Offset",
|
||||||
"Exclude some distance from the start of the path before the first instance.",
|
node_properties::SAMPLE_POLYLINE_TOOLTIP_START_OFFSET,
|
||||||
WidgetOverride::Number(NumberInputSettings {
|
WidgetOverride::Number(NumberInputSettings {
|
||||||
min: Some(0.),
|
min: Some(0.),
|
||||||
unit: Some(" px".to_string()),
|
unit: Some(" px".to_string()),
|
||||||
|
@ -1957,21 +1971,21 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||||
),
|
),
|
||||||
PropertiesRow::with_override(
|
PropertiesRow::with_override(
|
||||||
"Stop Offset",
|
"Stop Offset",
|
||||||
"Exclude some distance from the end of the path after the last instance.",
|
node_properties::SAMPLE_POLYLINE_TOOLTIP_STOP_OFFSET,
|
||||||
WidgetOverride::Number(NumberInputSettings {
|
WidgetOverride::Number(NumberInputSettings {
|
||||||
min: Some(0.),
|
min: Some(0.),
|
||||||
unit: Some(" px".to_string()),
|
unit: Some(" px".to_string()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
Into::<PropertiesRow>::into(("Adaptive Spacing", "Round 'Spacing' to a nearby value that divides into the path length evenly.")),
|
Into::<PropertiesRow>::into(("Adaptive Spacing", node_properties::SAMPLE_POLYLINE_TOOLTIP_ADAPTIVE_SPACING)),
|
||||||
],
|
],
|
||||||
output_names: vec!["Vector".to_string()],
|
output_names: vec!["Vector".to_string()],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
description: Cow::Borrowed("Convert vector geometry into a polyline composed of evenly spaced points."),
|
description: Cow::Borrowed("Convert vector geometry into a polyline composed of evenly spaced points."),
|
||||||
properties: None,
|
properties: Some("sample_polyline_properties"),
|
||||||
},
|
},
|
||||||
DocumentNodeDefinition {
|
DocumentNodeDefinition {
|
||||||
identifier: "Scatter Points",
|
identifier: "Scatter Points",
|
||||||
|
@ -2344,6 +2358,7 @@ fn static_node_properties() -> NodeProperties {
|
||||||
map.insert("math_properties".to_string(), Box::new(node_properties::math_properties));
|
map.insert("math_properties".to_string(), Box::new(node_properties::math_properties));
|
||||||
map.insert("rectangle_properties".to_string(), Box::new(node_properties::rectangle_properties));
|
map.insert("rectangle_properties".to_string(), Box::new(node_properties::rectangle_properties));
|
||||||
map.insert("grid_properties".to_string(), Box::new(node_properties::grid_properties));
|
map.insert("grid_properties".to_string(), Box::new(node_properties::grid_properties));
|
||||||
|
map.insert("sample_polyline_properties".to_string(), Box::new(node_properties::sample_polyline_properties));
|
||||||
map.insert(
|
map.insert(
|
||||||
"identity_properties".to_string(),
|
"identity_properties".to_string(),
|
||||||
Box::new(|_node_id, _context| node_properties::string_properties("The identity node simply passes its data through.")),
|
Box::new(|_node_id, _context| node_properties::string_properties("The identity node simply passes its data through.")),
|
||||||
|
|
|
@ -22,9 +22,9 @@ use graphene_std::raster_types::{CPU, GPU, RasterDataTable};
|
||||||
use graphene_std::text::Font;
|
use graphene_std::text::Font;
|
||||||
use graphene_std::transform::{Footprint, ReferencePoint};
|
use graphene_std::transform::{Footprint, ReferencePoint};
|
||||||
use graphene_std::vector::VectorDataTable;
|
use graphene_std::vector::VectorDataTable;
|
||||||
use graphene_std::vector::misc::CentroidType;
|
|
||||||
use graphene_std::vector::misc::{ArcType, MergeByDistanceAlgorithm};
|
use graphene_std::vector::misc::{ArcType, MergeByDistanceAlgorithm};
|
||||||
use graphene_std::vector::misc::{BooleanOperation, GridType};
|
use graphene_std::vector::misc::{BooleanOperation, GridType};
|
||||||
|
use graphene_std::vector::misc::{CentroidType, PointSpacingType};
|
||||||
use graphene_std::vector::style::{Fill, FillChoice, FillType, GradientStops};
|
use graphene_std::vector::style::{Fill, FillChoice, FillType, GradientStops};
|
||||||
use graphene_std::vector::style::{GradientType, PaintOrder, StrokeAlign, StrokeCap, StrokeJoin};
|
use graphene_std::vector::style::{GradientType, PaintOrder, StrokeAlign, StrokeCap, StrokeJoin};
|
||||||
use graphene_std::{GraphicGroupTable, NodeInputDecleration};
|
use graphene_std::{GraphicGroupTable, NodeInputDecleration};
|
||||||
|
@ -238,6 +238,7 @@ pub(crate) fn property_from_type(
|
||||||
Some(x) if x == TypeId::of::<PaintOrder>() => enum_choice::<PaintOrder>().for_socket(default_info).property_row(),
|
Some(x) if x == TypeId::of::<PaintOrder>() => enum_choice::<PaintOrder>().for_socket(default_info).property_row(),
|
||||||
Some(x) if x == TypeId::of::<ArcType>() => enum_choice::<ArcType>().for_socket(default_info).property_row(),
|
Some(x) if x == TypeId::of::<ArcType>() => enum_choice::<ArcType>().for_socket(default_info).property_row(),
|
||||||
Some(x) if x == TypeId::of::<MergeByDistanceAlgorithm>() => enum_choice::<MergeByDistanceAlgorithm>().for_socket(default_info).property_row(),
|
Some(x) if x == TypeId::of::<MergeByDistanceAlgorithm>() => enum_choice::<MergeByDistanceAlgorithm>().for_socket(default_info).property_row(),
|
||||||
|
Some(x) if x == TypeId::of::<PointSpacingType>() => enum_choice::<PointSpacingType>().for_socket(default_info).property_row(),
|
||||||
Some(x) if x == TypeId::of::<BooleanOperation>() => enum_choice::<BooleanOperation>().for_socket(default_info).property_row(),
|
Some(x) if x == TypeId::of::<BooleanOperation>() => enum_choice::<BooleanOperation>().for_socket(default_info).property_row(),
|
||||||
Some(x) if x == TypeId::of::<CentroidType>() => enum_choice::<CentroidType>().for_socket(default_info).property_row(),
|
Some(x) if x == TypeId::of::<CentroidType>() => enum_choice::<CentroidType>().for_socket(default_info).property_row(),
|
||||||
Some(x) if x == TypeId::of::<LuminanceCalculation>() => enum_choice::<LuminanceCalculation>().for_socket(default_info).property_row(),
|
Some(x) if x == TypeId::of::<LuminanceCalculation>() => enum_choice::<LuminanceCalculation>().for_socket(default_info).property_row(),
|
||||||
|
@ -1225,6 +1226,64 @@ pub(crate) fn grid_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
||||||
widgets
|
widgets
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) const SAMPLE_POLYLINE_TOOLTIP_SPACING: &str = "Use a point sampling density controlled by a distance between, or specific number of, points.";
|
||||||
|
pub(crate) const SAMPLE_POLYLINE_TOOLTIP_SEPARATION: &str = "Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).";
|
||||||
|
pub(crate) const SAMPLE_POLYLINE_TOOLTIP_QUANTITY: &str = "Number of points to place along the path.";
|
||||||
|
pub(crate) const SAMPLE_POLYLINE_TOOLTIP_START_OFFSET: &str = "Exclude some distance from the start of the path before the first instance.";
|
||||||
|
pub(crate) const SAMPLE_POLYLINE_TOOLTIP_STOP_OFFSET: &str = "Exclude some distance from the end of the path after the last instance.";
|
||||||
|
pub(crate) const SAMPLE_POLYLINE_TOOLTIP_ADAPTIVE_SPACING: &str = "Round 'Separation' to a nearby value that divides into the path length evenly.";
|
||||||
|
|
||||||
|
pub(crate) fn sample_polyline_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||||
|
use graphene_std::vector::sample_polyline::*;
|
||||||
|
|
||||||
|
let document_node = match get_document_node(node_id, context) {
|
||||||
|
Ok(document_node) => document_node,
|
||||||
|
Err(err) => {
|
||||||
|
log::error!("Could not get document node in sample_polyline_properties: {err}");
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let current_spacing = document_node.inputs.get(SpacingInput::INDEX).and_then(|input| input.as_value()).cloned();
|
||||||
|
let is_quantity = matches!(current_spacing, Some(TaggedValue::PointSpacingType(PointSpacingType::Quantity)));
|
||||||
|
|
||||||
|
let spacing = enum_choice::<PointSpacingType>()
|
||||||
|
.for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, SpacingInput::INDEX, true, context))
|
||||||
|
.property_row();
|
||||||
|
let separation = number_widget(
|
||||||
|
ParameterWidgetsInfo::from_index(document_node, node_id, SeparationInput::INDEX, true, context),
|
||||||
|
NumberInput::default().min(0.).unit(" px"),
|
||||||
|
);
|
||||||
|
let quantity = number_widget(
|
||||||
|
ParameterWidgetsInfo::from_index(document_node, node_id, QuantityInput::INDEX, true, context),
|
||||||
|
NumberInput::default().min(2.).int(),
|
||||||
|
);
|
||||||
|
let start_offset = number_widget(
|
||||||
|
ParameterWidgetsInfo::from_index(document_node, node_id, StartOffsetInput::INDEX, true, context),
|
||||||
|
NumberInput::default().min(0.).unit(" px"),
|
||||||
|
);
|
||||||
|
let stop_offset = number_widget(
|
||||||
|
ParameterWidgetsInfo::from_index(document_node, node_id, StopOffsetInput::INDEX, true, context),
|
||||||
|
NumberInput::default().min(0.).unit(" px"),
|
||||||
|
);
|
||||||
|
let adaptive_spacing = bool_widget(
|
||||||
|
ParameterWidgetsInfo::from_index(document_node, node_id, AdaptiveSpacingInput::INDEX, true, context),
|
||||||
|
CheckboxInput::default().disabled(is_quantity),
|
||||||
|
);
|
||||||
|
|
||||||
|
vec![
|
||||||
|
spacing.with_tooltip(SAMPLE_POLYLINE_TOOLTIP_SPACING),
|
||||||
|
match current_spacing {
|
||||||
|
Some(TaggedValue::PointSpacingType(PointSpacingType::Separation)) => LayoutGroup::Row { widgets: separation }.with_tooltip(SAMPLE_POLYLINE_TOOLTIP_SEPARATION),
|
||||||
|
Some(TaggedValue::PointSpacingType(PointSpacingType::Quantity)) => LayoutGroup::Row { widgets: quantity }.with_tooltip(SAMPLE_POLYLINE_TOOLTIP_QUANTITY),
|
||||||
|
_ => LayoutGroup::Row { widgets: vec![] },
|
||||||
|
},
|
||||||
|
LayoutGroup::Row { widgets: start_offset }.with_tooltip(SAMPLE_POLYLINE_TOOLTIP_START_OFFSET),
|
||||||
|
LayoutGroup::Row { widgets: stop_offset }.with_tooltip(SAMPLE_POLYLINE_TOOLTIP_STOP_OFFSET),
|
||||||
|
LayoutGroup::Row { widgets: adaptive_spacing }.with_tooltip(SAMPLE_POLYLINE_TOOLTIP_ADAPTIVE_SPACING),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn exposure_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
pub(crate) fn exposure_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||||
use graphene_std::raster::exposure::*;
|
use graphene_std::raster::exposure::*;
|
||||||
|
|
||||||
|
|
|
@ -441,7 +441,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
|
||||||
let document_name = document_name.replace("__DO_NOT_UPGRADE__", "");
|
let document_name = document_name.replace("__DO_NOT_UPGRADE__", "");
|
||||||
|
|
||||||
const TEXT_REPLACEMENTS: [(&str, &str); 2] = [
|
const TEXT_REPLACEMENTS: [(&str, &str); 2] = [
|
||||||
("graphene_core::vector::vector_nodes::SamplePointsNode", "graphene_core::vector::SamplePointsNode"),
|
("graphene_core::vector::vector_nodes::SamplePointsNode", "graphene_core::vector::SamplePolylineNode"),
|
||||||
("graphene_core::vector::vector_nodes::SubpathSegmentLengthsNode", "graphene_core::vector::SubpathSegmentLengthsNode"),
|
("graphene_core::vector::vector_nodes::SubpathSegmentLengthsNode", "graphene_core::vector::SubpathSegmentLengthsNode"),
|
||||||
];
|
];
|
||||||
let document_serialized_content = TEXT_REPLACEMENTS
|
let document_serialized_content = TEXT_REPLACEMENTS
|
||||||
|
@ -1074,6 +1074,31 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
|
||||||
|
|
||||||
document.network_interface.replace_reference_name(node_id, network_path, "Merge by Distance".to_string());
|
document.network_interface.replace_reference_name(node_id, network_path, "Merge by Distance".to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if reference == "Sample Points" && inputs_count == 5 {
|
||||||
|
// TODO: Rename to "Sample Polyline", also remove segment generation from "Scatter Points"
|
||||||
|
let node_definition = resolve_document_node_type("Sample Polyline").unwrap();
|
||||||
|
let new_node_template = node_definition.default_node_template();
|
||||||
|
let document_node = new_node_template.document_node;
|
||||||
|
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||||
|
document
|
||||||
|
.network_interface
|
||||||
|
.replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata);
|
||||||
|
|
||||||
|
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||||
|
let new_spacing_value = NodeInput::value(TaggedValue::PointSpacingType(graphene_std::vector::misc::PointSpacingType::Separation), false);
|
||||||
|
|
||||||
|
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||||
|
document.network_interface.set_input(&InputConnector::node(*node_id, 1), new_spacing_value, network_path);
|
||||||
|
document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[1].clone(), network_path);
|
||||||
|
document.network_interface.set_input(&InputConnector::node(*node_id, 3), old_inputs[1].clone(), network_path);
|
||||||
|
document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[2].clone(), network_path);
|
||||||
|
document.network_interface.set_input(&InputConnector::node(*node_id, 5), old_inputs[3].clone(), network_path);
|
||||||
|
document.network_interface.set_input(&InputConnector::node(*node_id, 6), old_inputs[4].clone(), network_path);
|
||||||
|
|
||||||
|
// TODO: Rename to "Sample Polyline", also remove segment generation from "Scatter Points"
|
||||||
|
document.network_interface.replace_reference_name(node_id, network_path, "Sample Polyline".to_string());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Eventually remove this document upgrade code
|
// TODO: Eventually remove this document upgrade code
|
||||||
|
|
|
@ -2,7 +2,7 @@ use crate::vector::VectorDataTable;
|
||||||
use crate::{Color, Context, Ctx};
|
use crate::{Color, Context, Ctx};
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
|
|
||||||
#[node_macro::node(category("Debug"))]
|
#[node_macro::node(category("Debug"), name("Log to Console"))]
|
||||||
fn log_to_console<T: std::fmt::Debug>(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, VectorDataTable, DAffine2, Color, Option<Color>)] value: T) -> T {
|
fn log_to_console<T: std::fmt::Debug>(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, VectorDataTable, DAffine2, Color, Option<Color>)] value: T) -> T {
|
||||||
// KEEP THIS `debug!()` - It acts as the output for the debug node itself
|
// KEEP THIS `debug!()` - It acts as the output for the debug node itself
|
||||||
log::debug!("{:#?}", value);
|
log::debug!("{:#?}", value);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use super::poisson_disk::poisson_disk_sample;
|
use super::poisson_disk::poisson_disk_sample;
|
||||||
use crate::vector::misc::dvec2_to_point;
|
use crate::vector::misc::{PointSpacingType, dvec2_to_point};
|
||||||
use glam::DVec2;
|
use glam::DVec2;
|
||||||
use kurbo::{BezPath, DEFAULT_ACCURACY, Line, ParamCurve, ParamCurveDeriv, PathEl, PathSeg, Point, Rect, Shape};
|
use kurbo::{BezPath, DEFAULT_ACCURACY, Line, ParamCurve, ParamCurveDeriv, PathEl, PathSeg, Point, Rect, Shape};
|
||||||
|
|
||||||
|
@ -67,7 +67,15 @@ pub fn tangent_on_bezpath(bezpath: &BezPath, t: f64, euclidian: bool, segments_l
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sample_points_on_bezpath(bezpath: BezPath, spacing: f64, start_offset: f64, stop_offset: f64, adaptive_spacing: bool, segments_length: &[f64]) -> Option<BezPath> {
|
pub fn sample_polyline_on_bezpath(
|
||||||
|
bezpath: BezPath,
|
||||||
|
point_spacing_type: PointSpacingType,
|
||||||
|
amount: f64,
|
||||||
|
start_offset: f64,
|
||||||
|
stop_offset: f64,
|
||||||
|
adaptive_spacing: bool,
|
||||||
|
segments_length: &[f64],
|
||||||
|
) -> Option<BezPath> {
|
||||||
let mut sample_bezpath = BezPath::new();
|
let mut sample_bezpath = BezPath::new();
|
||||||
|
|
||||||
let was_closed = matches!(bezpath.elements().last(), Some(PathEl::ClosePath));
|
let was_closed = matches!(bezpath.elements().last(), Some(PathEl::ClosePath));
|
||||||
|
@ -78,22 +86,33 @@ pub fn sample_points_on_bezpath(bezpath: BezPath, spacing: f64, start_offset: f6
|
||||||
// Adjust the usable length by subtracting start and stop offsets.
|
// Adjust the usable length by subtracting start and stop offsets.
|
||||||
let mut used_length = total_length - start_offset - stop_offset;
|
let mut used_length = total_length - start_offset - stop_offset;
|
||||||
|
|
||||||
|
// Sanity check that the usable length is positive.
|
||||||
if used_length <= 0. {
|
if used_length <= 0. {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine the number of points to generate along the path.
|
const SAFETY_MAX_COUNT: f64 = 10_000. - 1.;
|
||||||
let sample_count = if adaptive_spacing {
|
|
||||||
// Calculate point count to evenly distribute points while covering the entire path.
|
|
||||||
// With adaptive spacing, we widen or narrow the points as necessary to ensure the last point is always at the end of the path.
|
|
||||||
(used_length / spacing).round()
|
|
||||||
} else {
|
|
||||||
// Calculate point count based on exact spacing, which may not cover the entire path.
|
|
||||||
|
|
||||||
// Without adaptive spacing, we just evenly space the points at the exact specified spacing, usually falling short before the end of the path.
|
// Determine the number of points to generate along the path.
|
||||||
let count = (used_length / spacing + f64::EPSILON).floor();
|
let sample_count = match point_spacing_type {
|
||||||
used_length -= used_length % spacing;
|
PointSpacingType::Separation => {
|
||||||
count
|
let spacing = amount.min(used_length - f64::EPSILON);
|
||||||
|
|
||||||
|
if adaptive_spacing {
|
||||||
|
// Calculate point count to evenly distribute points while covering the entire path.
|
||||||
|
// With adaptive spacing, we widen or narrow the points as necessary to ensure the last point is always at the end of the path.
|
||||||
|
(used_length / spacing).round().min(SAFETY_MAX_COUNT)
|
||||||
|
} else {
|
||||||
|
// Calculate point count based on exact spacing, which may not cover the entire path.
|
||||||
|
// Without adaptive spacing, we just evenly space the points at the exact specified spacing, usually falling short before the end of the path.
|
||||||
|
let count = (used_length / spacing + f64::EPSILON).floor().min(SAFETY_MAX_COUNT);
|
||||||
|
if count != SAFETY_MAX_COUNT {
|
||||||
|
used_length -= used_length % spacing;
|
||||||
|
}
|
||||||
|
count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PointSpacingType::Quantity => (amount - 1.).floor().clamp(1., SAFETY_MAX_COUNT),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Skip if there are no points to generate.
|
// Skip if there are no points to generate.
|
||||||
|
|
|
@ -103,6 +103,17 @@ pub enum MergeByDistanceAlgorithm {
|
||||||
Topological,
|
Topological,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, specta::Type, node_macro::ChoiceType)]
|
||||||
|
#[widget(Radio)]
|
||||||
|
pub enum PointSpacingType {
|
||||||
|
#[default]
|
||||||
|
/// The desired spacing distance between points.
|
||||||
|
Separation,
|
||||||
|
/// The exact number of points to span the path.
|
||||||
|
Quantity,
|
||||||
|
}
|
||||||
|
|
||||||
pub fn point_to_dvec2(point: Point) -> DVec2 {
|
pub fn point_to_dvec2(point: Point) -> DVec2 {
|
||||||
DVec2 { x: point.x, y: point.y }
|
DVec2 { x: point.x, y: point.y }
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use super::algorithms::bezpath_algorithms::{self, position_on_bezpath, sample_points_on_bezpath, split_bezpath, tangent_on_bezpath};
|
use super::algorithms::bezpath_algorithms::{self, position_on_bezpath, sample_polyline_on_bezpath, split_bezpath, tangent_on_bezpath};
|
||||||
use super::algorithms::offset_subpath::offset_subpath;
|
use super::algorithms::offset_subpath::offset_subpath;
|
||||||
use super::algorithms::spline::{solve_spline_first_handle_closed, solve_spline_first_handle_open};
|
use super::algorithms::spline::{solve_spline_first_handle_closed, solve_spline_first_handle_open};
|
||||||
use super::misc::{CentroidType, point_to_dvec2};
|
use super::misc::{CentroidType, point_to_dvec2};
|
||||||
|
@ -9,7 +9,7 @@ use crate::raster_types::{CPU, GPU, RasterDataTable};
|
||||||
use crate::registry::types::{Angle, Fraction, IntegerCount, Length, Multiplier, Percentage, PixelLength, PixelSize, SeedValue};
|
use crate::registry::types::{Angle, Fraction, IntegerCount, Length, Multiplier, Percentage, PixelLength, PixelSize, SeedValue};
|
||||||
use crate::renderer::GraphicElementRendered;
|
use crate::renderer::GraphicElementRendered;
|
||||||
use crate::transform::{Footprint, ReferencePoint, Transform};
|
use crate::transform::{Footprint, ReferencePoint, Transform};
|
||||||
use crate::vector::misc::{MergeByDistanceAlgorithm, dvec2_to_point};
|
use crate::vector::misc::{MergeByDistanceAlgorithm, PointSpacingType, dvec2_to_point};
|
||||||
use crate::vector::style::{PaintOrder, StrokeAlign, StrokeCap, StrokeJoin};
|
use crate::vector::style::{PaintOrder, StrokeAlign, StrokeCap, StrokeJoin};
|
||||||
use crate::vector::{FillId, PointDomain, RegionId};
|
use crate::vector::{FillId, PointDomain, RegionId};
|
||||||
use crate::{CloneVarArgs, Color, Context, Ctx, ExtractAll, GraphicElement, GraphicGroupTable, OwnedContextImpl};
|
use crate::{CloneVarArgs, Color, Context, Ctx, ExtractAll, GraphicElement, GraphicGroupTable, OwnedContextImpl};
|
||||||
|
@ -1141,11 +1141,19 @@ where
|
||||||
output_table
|
output_table
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convert vector geometry into a polyline composed of evenly spaced points.
|
||||||
#[node_macro::node(category(""), path(graphene_core::vector))]
|
#[node_macro::node(category(""), path(graphene_core::vector))]
|
||||||
async fn sample_points(_: impl Ctx, vector_data: VectorDataTable, spacing: f64, start_offset: f64, stop_offset: f64, adaptive_spacing: bool, subpath_segment_lengths: Vec<f64>) -> VectorDataTable {
|
async fn sample_polyline(
|
||||||
// Limit the smallest spacing to something sensible to avoid freezing the application.
|
_: impl Ctx,
|
||||||
let spacing = spacing.max(0.01);
|
vector_data: VectorDataTable,
|
||||||
|
spacing: PointSpacingType,
|
||||||
|
separation: f64,
|
||||||
|
quantity: f64,
|
||||||
|
start_offset: f64,
|
||||||
|
stop_offset: f64,
|
||||||
|
adaptive_spacing: bool,
|
||||||
|
subpath_segment_lengths: Vec<f64>,
|
||||||
|
) -> VectorDataTable {
|
||||||
let mut result_table = VectorDataTable::default();
|
let mut result_table = VectorDataTable::default();
|
||||||
|
|
||||||
for mut vector_data_instance in vector_data.instance_iter() {
|
for mut vector_data_instance in vector_data.instance_iter() {
|
||||||
|
@ -1180,7 +1188,11 @@ async fn sample_points(_: impl Ctx, vector_data: VectorDataTable, spacing: f64,
|
||||||
// Increment the segment index by the number of segments in the current bezpath to calculate the next bezpath segment's length.
|
// Increment the segment index by the number of segments in the current bezpath to calculate the next bezpath segment's length.
|
||||||
next_segment_index += segment_count;
|
next_segment_index += segment_count;
|
||||||
|
|
||||||
let Some(mut sample_bezpath) = sample_points_on_bezpath(bezpath, spacing, start_offset, stop_offset, adaptive_spacing, current_bezpath_segments_length) else {
|
let amount = match spacing {
|
||||||
|
PointSpacingType::Separation => separation,
|
||||||
|
PointSpacingType::Quantity => quantity,
|
||||||
|
};
|
||||||
|
let Some(mut sample_bezpath) = sample_polyline_on_bezpath(bezpath, spacing, amount, start_offset, stop_offset, adaptive_spacing, current_bezpath_segments_length) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -2073,41 +2085,41 @@ mod test {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn sample_points() {
|
async fn sample_polyline() {
|
||||||
let path = Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.));
|
let path = Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.));
|
||||||
let sample_points = super::sample_points(Footprint::default(), vector_node(path), 30., 0., 0., false, vec![100.]).await;
|
let sample_polyline = super::sample_polyline(Footprint::default(), vector_node(path), PointSpacingType::Separation, 30., 0., 0., 0., false, vec![100.]).await;
|
||||||
let sample_points = sample_points.instance_ref_iter().next().unwrap().instance;
|
let sample_polyline = sample_polyline.instance_ref_iter().next().unwrap().instance;
|
||||||
assert_eq!(sample_points.point_domain.positions().len(), 4);
|
assert_eq!(sample_polyline.point_domain.positions().len(), 4);
|
||||||
for (pos, expected) in sample_points.point_domain.positions().iter().zip([DVec2::X * 0., DVec2::X * 30., DVec2::X * 60., DVec2::X * 90.]) {
|
for (pos, expected) in sample_polyline.point_domain.positions().iter().zip([DVec2::X * 0., DVec2::X * 30., DVec2::X * 60., DVec2::X * 90.]) {
|
||||||
assert!(pos.distance(expected) < 1e-3, "Expected {expected} found {pos}");
|
assert!(pos.distance(expected) < 1e-3, "Expected {expected} found {pos}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn adaptive_spacing() {
|
async fn sample_polyline_adaptive_spacing() {
|
||||||
let path = Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.));
|
let path = Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.));
|
||||||
let sample_points = super::sample_points(Footprint::default(), vector_node(path), 18., 45., 10., true, vec![100.]).await;
|
let sample_polyline = super::sample_polyline(Footprint::default(), vector_node(path), PointSpacingType::Separation, 18., 0., 45., 10., true, vec![100.]).await;
|
||||||
let sample_points = sample_points.instance_ref_iter().next().unwrap().instance;
|
let sample_polyline = sample_polyline.instance_ref_iter().next().unwrap().instance;
|
||||||
assert_eq!(sample_points.point_domain.positions().len(), 4);
|
assert_eq!(sample_polyline.point_domain.positions().len(), 4);
|
||||||
for (pos, expected) in sample_points.point_domain.positions().iter().zip([DVec2::X * 45., DVec2::X * 60., DVec2::X * 75., DVec2::X * 90.]) {
|
for (pos, expected) in sample_polyline.point_domain.positions().iter().zip([DVec2::X * 45., DVec2::X * 60., DVec2::X * 75., DVec2::X * 90.]) {
|
||||||
assert!(pos.distance(expected) < 1e-3, "Expected {expected} found {pos}");
|
assert!(pos.distance(expected) < 1e-3, "Expected {expected} found {pos}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn poisson() {
|
async fn poisson() {
|
||||||
let sample_points = super::poisson_disk_points(
|
let poisson_points = super::poisson_disk_points(
|
||||||
Footprint::default(),
|
Footprint::default(),
|
||||||
vector_node(Subpath::new_ellipse(DVec2::NEG_ONE * 50., DVec2::ONE * 50.)),
|
vector_node(Subpath::new_ellipse(DVec2::NEG_ONE * 50., DVec2::ONE * 50.)),
|
||||||
10. * std::f64::consts::SQRT_2,
|
10. * std::f64::consts::SQRT_2,
|
||||||
0,
|
0,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
let sample_points = sample_points.instance_ref_iter().next().unwrap().instance;
|
let poisson_points = poisson_points.instance_ref_iter().next().unwrap().instance;
|
||||||
assert!(
|
assert!(
|
||||||
(20..=40).contains(&sample_points.point_domain.positions().len()),
|
(20..=40).contains(&poisson_points.point_domain.positions().len()),
|
||||||
"actual len {}",
|
"actual len {}",
|
||||||
sample_points.point_domain.positions().len()
|
poisson_points.point_domain.positions().len()
|
||||||
);
|
);
|
||||||
for point in sample_points.point_domain.positions() {
|
for point in poisson_points.point_domain.positions() {
|
||||||
assert!(point.length() < 50. + 1., "Expected point in circle {point}")
|
assert!(point.length() < 50. + 1., "Expected point in circle {point}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2126,8 +2138,8 @@ mod test {
|
||||||
|
|
||||||
let length = super::path_length(Footprint::default(), vector_node_from_instances(instances)).await;
|
let length = super::path_length(Footprint::default(), vector_node_from_instances(instances)).await;
|
||||||
|
|
||||||
// 4040 equals 101 * 4 (rectangle perimeter) * 2 (scale) * 5 (number of rows)
|
// 101 (each rectangle edge length) * 4 (rectangle perimeter) * 2 (scale) * 5 (number of rows)
|
||||||
assert_eq!(length, 4040.);
|
assert_eq!(length, 101. * 4. * 2. * 5.);
|
||||||
}
|
}
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn spline() {
|
async fn spline() {
|
||||||
|
@ -2140,10 +2152,10 @@ mod test {
|
||||||
async fn morph() {
|
async fn morph() {
|
||||||
let source = Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.);
|
let source = Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.);
|
||||||
let target = Subpath::new_ellipse(DVec2::NEG_ONE * 100., DVec2::ZERO);
|
let target = Subpath::new_ellipse(DVec2::NEG_ONE * 100., DVec2::ZERO);
|
||||||
let sample_points = super::morph(Footprint::default(), vector_node(source), vector_node(target), 0.5).await;
|
let morphed = super::morph(Footprint::default(), vector_node(source), vector_node(target), 0.5).await;
|
||||||
let sample_points = sample_points.instance_ref_iter().next().unwrap().instance;
|
let morphed = morphed.instance_ref_iter().next().unwrap().instance;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
&sample_points.point_domain.positions()[..4],
|
&morphed.point_domain.positions()[..4],
|
||||||
vec![DVec2::new(-25., -50.), DVec2::new(50., -25.), DVec2::new(25., 50.), DVec2::new(-50., 25.)]
|
vec![DVec2::new(-25., -50.), DVec2::new(50., -25.), DVec2::new(25., 50.), DVec2::new(-50., 25.)]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -235,6 +235,7 @@ tagged_value! {
|
||||||
GridType(graphene_core::vector::misc::GridType),
|
GridType(graphene_core::vector::misc::GridType),
|
||||||
ArcType(graphene_core::vector::misc::ArcType),
|
ArcType(graphene_core::vector::misc::ArcType),
|
||||||
MergeByDistanceAlgorithm(graphene_core::vector::misc::MergeByDistanceAlgorithm),
|
MergeByDistanceAlgorithm(graphene_core::vector::misc::MergeByDistanceAlgorithm),
|
||||||
|
PointSpacingType(graphene_core::vector::misc::PointSpacingType),
|
||||||
#[serde(alias = "LineCap")]
|
#[serde(alias = "LineCap")]
|
||||||
StrokeCap(graphene_core::vector::style::StrokeCap),
|
StrokeCap(graphene_core::vector::style::StrokeCap),
|
||||||
#[serde(alias = "LineJoin")]
|
#[serde(alias = "LineJoin")]
|
||||||
|
|
|
@ -70,6 +70,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
|
||||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Color]),
|
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Color]),
|
||||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Box<graphene_core::vector::VectorModification>]),
|
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Box<graphene_core::vector::VectorModification>]),
|
||||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::CentroidType]),
|
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::CentroidType]),
|
||||||
|
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::PointSpacingType]),
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Image<Color>]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Image<Color>]),
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => VectorDataTable]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => VectorDataTable]),
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => RasterDataTable<CPU>]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => RasterDataTable<CPU>]),
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue