mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
Replace the 'Vec2 to Point' node with automatic conversion machinery (#4236)
* Replace the 'Vec2 to Point' node with an 'As Vector' node and automatic conversion * Accept a Vec2 as content for the Wrap Graphic node * Rename the type-assertion nodes from 'To' to 'As' * Fix the Transform node reversing a Vec2 input's direction * Fix text migration bug
This commit is contained in:
2
demo-artwork/changing-seasons.graphite
generated
2
demo-artwork/changing-seasons.graphite
generated
File diff suppressed because one or more lines are too long
2
demo-artwork/isometric-fountain.graphite
generated
2
demo-artwork/isometric-fountain.graphite
generated
File diff suppressed because one or more lines are too long
2
demo-artwork/marbled-mandelbrot.graphite
generated
2
demo-artwork/marbled-mandelbrot.graphite
generated
File diff suppressed because one or more lines are too long
2
demo-artwork/painted-dreams.graphite
generated
2
demo-artwork/painted-dreams.graphite
generated
File diff suppressed because one or more lines are too long
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
2
demo-artwork/valley-of-spires.graphite
generated
2
demo-artwork/valley-of-spires.graphite
generated
File diff suppressed because one or more lines are too long
@@ -787,9 +787,9 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
|
||||
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
||||
..Default::default()
|
||||
},
|
||||
// 3: Vec2 to Point
|
||||
// 3: As Vector (auto-converts the decomposed translation into a single-anchor List<Vector>)
|
||||
DocumentNode {
|
||||
implementation: DocumentNodeImplementation::ProtoNode(vector_nodes::vec_2_to_point::IDENTIFIER),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(vector::as_vector::IDENTIFIER),
|
||||
inputs: vec![NodeInput::node(NodeId(2), 0)],
|
||||
..Default::default()
|
||||
},
|
||||
@@ -858,7 +858,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
// 3: Vec2 to Point
|
||||
// 3: As Vector
|
||||
DocumentNodeMetadata {
|
||||
persistent_metadata: DocumentNodePersistentMetadata {
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(21, 1)),
|
||||
|
||||
@@ -404,16 +404,16 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
|
||||
aliases: &["graphene_math_nodes::TangentInverseNode", "graphene_core::ops::TangentInverseNode"],
|
||||
},
|
||||
NodeReplacement {
|
||||
node: graphene_std::math_nodes::to_f_64::IDENTIFIER,
|
||||
aliases: &["graphene_math_nodes::ToF64Node", "graphene_core::ops::ToF64Node"],
|
||||
node: graphene_std::math_nodes::as_f_64::IDENTIFIER,
|
||||
aliases: &["graphene_math_nodes::ToF64Node", "graphene_core::ops::ToF64Node", "math_nodes::ToF64Node"],
|
||||
},
|
||||
NodeReplacement {
|
||||
node: graphene_std::math_nodes::to_u_32::IDENTIFIER,
|
||||
node: graphene_std::math_nodes::as_u_32::IDENTIFIER,
|
||||
aliases: &["graphene_math_nodes::ToU32Node", "graphene_core::ops::ToU32Node", "math_nodes::ToU32Node"],
|
||||
},
|
||||
NodeReplacement {
|
||||
node: graphene_std::math_nodes::to_u_64::IDENTIFIER,
|
||||
aliases: &["graphene_math_nodes::ToU64Node", "graphene_core::ops::ToU64Node"],
|
||||
node: graphene_std::math_nodes::as_u_64::IDENTIFIER,
|
||||
aliases: &["graphene_math_nodes::ToU64Node", "graphene_core::ops::ToU64Node", "math_nodes::ToU64Node"],
|
||||
},
|
||||
NodeReplacement {
|
||||
node: graphene_std::math_nodes::vec_2_value::IDENTIFIER,
|
||||
@@ -676,8 +676,8 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
|
||||
aliases: &["graphene_core::logic::SwitchNode"],
|
||||
},
|
||||
NodeReplacement {
|
||||
node: graphene_std::text_nodes::to_string::IDENTIFIER,
|
||||
aliases: &["graphene_core::logic::ToStringNode"],
|
||||
node: graphene_std::text_nodes::as_string::IDENTIFIER,
|
||||
aliases: &["graphene_core::logic::ToStringNode", "text_nodes::ToStringNode"],
|
||||
},
|
||||
NodeReplacement {
|
||||
node: graphene_std::text_nodes::json::query_json::IDENTIFIER,
|
||||
@@ -958,11 +958,12 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
|
||||
aliases: &["graphene_core::vector::TangentOnPathNode"],
|
||||
},
|
||||
NodeReplacement {
|
||||
node: graphene_std::vector::vec_2_to_point::IDENTIFIER,
|
||||
node: graphene_std::vector::as_vector::IDENTIFIER,
|
||||
aliases: &[
|
||||
"graphene_core::vector::vector_nodes::PositionToPointNode",
|
||||
"graphene_core::vector::PositionToPointNode",
|
||||
"graphene_core::vector::Vec2ToPointNode",
|
||||
"core_types::vector::Vec2ToPointNode",
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -1574,6 +1575,8 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
|
||||
for i in 10..=12 {
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, i), old_inputs[i - 2].clone(), network_path);
|
||||
}
|
||||
|
||||
inputs_count = 13;
|
||||
}
|
||||
|
||||
// Upgrade Sine, Cosine, and Tangent nodes to include a boolean input for whether the output should be in radians, which was previously the only option but is now not the default
|
||||
|
||||
@@ -33,7 +33,6 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
|
||||
// INTO NODES
|
||||
// ==========
|
||||
into_node!(from: List<Graphic>, to: List<Graphic>),
|
||||
into_node!(from: List<Vector>, to: List<Vector>),
|
||||
into_node!(from: List<Raster<CPU>>, to: List<Raster<CPU>>),
|
||||
#[cfg(feature = "gpu")]
|
||||
into_node!(from: List<Raster<GPU>>, to: List<Raster<GPU>>),
|
||||
@@ -96,6 +95,8 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
|
||||
#[cfg(feature = "gpu")]
|
||||
into_node!(from: &PlatformEditorApi, to: &WgpuExecutor),
|
||||
convert_node!(from: DVec2, to: DVec2),
|
||||
convert_node!(from: List<Vector>, to: List<Vector>),
|
||||
convert_node!(from: DVec2, to: List<Vector>),
|
||||
convert_node!(from: String, to: String),
|
||||
convert_node!(from: bool, to: String),
|
||||
convert_node!(from: DVec2, to: String),
|
||||
|
||||
@@ -106,7 +106,19 @@ impl Convert<DVec2, ()> for DVec2 {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Add a DVec2 to List<Vector> anchor point conversion implementation to replace the 'Vec2 to Point' node
|
||||
/// Constructs `Self` from a single anchor point at the given position. Implemented by the vector crate's
|
||||
/// path type so the `Convert` impl below can build a single-point path without core-types depending on
|
||||
/// that crate (mirroring how [`ListConvert`] bridges per-item list conversions).
|
||||
pub trait FromAnchorPosition {
|
||||
fn from_anchor_position(position: DVec2) -> Self;
|
||||
}
|
||||
|
||||
// Converts a position into a vector path composed of a single anchor point
|
||||
impl<T: FromAnchorPosition + Send> Convert<List<T>, ()> for DVec2 {
|
||||
async fn convert(self, _: Footprint, _: ()) -> List<T> {
|
||||
List::new_from_item(Item::new_from_element(T::from_anchor_position(self)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements the [`Convert`] trait for conversion between the cartesian product of Rust's primitive numeric types.
|
||||
macro_rules! impl_convert {
|
||||
|
||||
@@ -234,6 +234,6 @@ impl ApplyTransform for DVec2 {
|
||||
*self = modification.transform_point2(*self);
|
||||
}
|
||||
fn left_apply_transform(&mut self, modification: &DAffine2) {
|
||||
*self = modification.inverse().transform_point2(*self);
|
||||
*self = modification.transform_point2(*self);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use core_types::bounds::{BoundingBox, RenderBoundingBox};
|
||||
use core_types::graphene_hash::CacheHash;
|
||||
use core_types::list::{ATTR_FILL, ATTR_STROKE, Item, List};
|
||||
use core_types::ops::ListConvert;
|
||||
use core_types::ops::{FromAnchorPosition, ListConvert};
|
||||
use core_types::render_complexity::RenderComplexity;
|
||||
use core_types::uuid::NodeId;
|
||||
use core_types::{ATTR_CLIPPING_MASK, ATTR_EDITOR_LAYER_PATH, ATTR_GRADIENT_TYPE, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_SPREAD_METHOD, ATTR_TRANSFORM, Color};
|
||||
use dyn_any::DynAny;
|
||||
use glam::DAffine2;
|
||||
use glam::{DAffine2, DVec2};
|
||||
use raster_types::{CPU, GPU, Raster};
|
||||
use std::borrow::Cow;
|
||||
use vector_types::GradientStops;
|
||||
@@ -393,6 +393,13 @@ impl From<DAffine2> for Graphic {
|
||||
Graphic::default()
|
||||
}
|
||||
}
|
||||
|
||||
// DVec2
|
||||
impl From<DVec2> for Graphic {
|
||||
fn from(position: DVec2) -> Self {
|
||||
Graphic::Vector(List::new_from_element(Vector::from_anchor_position(position)))
|
||||
}
|
||||
}
|
||||
// Note: List conversions handled by blanket impl in gcore
|
||||
|
||||
impl Graphic {
|
||||
|
||||
@@ -55,6 +55,23 @@ impl graphene_hash::CacheHash for Vector {
|
||||
}
|
||||
}
|
||||
|
||||
impl core_types::ops::FromAnchorPosition for Vector {
|
||||
fn from_anchor_position(position: DVec2) -> Self {
|
||||
let mut point_domain = PointDomain::new();
|
||||
point_domain.push(PointId::generate(), position);
|
||||
|
||||
Self { point_domain, ..Default::default() }
|
||||
}
|
||||
}
|
||||
|
||||
// Identity item conversion so `List<Vector>` satisfies the blanket `Convert<List<U>, ()> for List<T>`, letting its
|
||||
// auto-inserted input wrapper be a `ConvertNode` (which also accepts a `DVec2` anchor position) rather than an `IntoNode`.
|
||||
impl core_types::ops::ListConvert<Vector> for Vector {
|
||||
fn convert_item(self) -> Vector {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Vector {
|
||||
/// Add a subpath to this vector path.
|
||||
pub fn append_subpath(&mut self, subpath: impl Borrow<Subpath<PointId>>, preserve_id: bool) {
|
||||
@@ -619,4 +636,17 @@ mod tests {
|
||||
let generated = vector.stroke_bezier_paths().collect::<Vec<_>>();
|
||||
assert_subpath_eq(&generated, &[curve, circle]);
|
||||
}
|
||||
|
||||
// Verifies the `DVec2 -> List<Vector>` conversion that replaced the former "Vec2 to Point" node yields a path
|
||||
// with exactly one anchor point at the given position and no segments
|
||||
#[test]
|
||||
fn anchor_position_builds_single_point_path() {
|
||||
use core_types::ops::FromAnchorPosition;
|
||||
|
||||
let vector = Vector::from_anchor_position(DVec2::new(3., 4.));
|
||||
|
||||
assert_eq!(vector.point_domain.positions(), [DVec2::new(3., 4.)]);
|
||||
assert_eq!(vector.point_domain.ids().len(), 1);
|
||||
assert!(vector.segment_domain.ids().is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -549,6 +549,7 @@ pub async fn wrap_graphic<T: Into<Graphic> + 'n>(
|
||||
List<Color>,
|
||||
List<GradientStops>,
|
||||
DAffine2,
|
||||
DVec2,
|
||||
)]
|
||||
content: T,
|
||||
) -> List<Graphic> {
|
||||
|
||||
@@ -421,22 +421,22 @@ fn random(
|
||||
|
||||
// TODO: Test that these are no longer needed in all circumstances, then remove them and add a migration to convert these into Passthrough nodes. Note: these act more as type annotations than as identity functions.
|
||||
/// Convert a number to an integer of the type u32, which may be the required type for certain node inputs.
|
||||
#[node_macro::node(name("To u32"), category("Debug"))]
|
||||
fn to_u32(_: impl Ctx, value: u32) -> u32 {
|
||||
#[node_macro::node(name("As u32"), category("Debug"))]
|
||||
fn as_u32(_: impl Ctx, value: u32) -> u32 {
|
||||
value
|
||||
}
|
||||
|
||||
// TODO: Test that these are no longer needed in all circumstances, then remove them and add a migration to convert these into Passthrough nodes. Note: these act more as type annotations than as identity functions.
|
||||
/// Convert a number to an integer of the type u64, which may be the required type for certain node inputs.
|
||||
#[node_macro::node(name("To u64"), category("Debug"))]
|
||||
fn to_u64(_: impl Ctx, value: u64) -> u64 {
|
||||
#[node_macro::node(name("As u64"), category("Debug"))]
|
||||
fn as_u64(_: impl Ctx, value: u64) -> u64 {
|
||||
value
|
||||
}
|
||||
|
||||
// TODO: Test that these are no longer needed in all circumstances, then remove them and add a migration to convert these into Passthrough nodes. Note: these act more as type annotations than as identity functions.
|
||||
/// Convert an integer to a decimal number of the type f64, which may be the required type for certain node inputs.
|
||||
#[node_macro::node(name("To f64"), category("Debug"))]
|
||||
fn to_f64(_: impl Ctx, value: f64) -> f64 {
|
||||
#[node_macro::node(name("As f64"), category("Debug"))]
|
||||
fn as_f64(_: impl Ctx, value: f64) -> f64 {
|
||||
value
|
||||
}
|
||||
|
||||
|
||||
@@ -188,7 +188,7 @@ fn string_value(_: impl Ctx, _primary: (), string: TextArea) -> String {
|
||||
|
||||
/// Type-asserts a value to be a string.
|
||||
#[node_macro::node(category("Debug"))]
|
||||
fn to_string(_: impl Ctx, value: String) -> String {
|
||||
fn as_string(_: impl Ctx, value: String) -> String {
|
||||
value
|
||||
}
|
||||
|
||||
|
||||
@@ -1074,16 +1074,10 @@ async fn dimensions(_: impl Ctx, content: List<Vector>) -> DVec2 {
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
// TODO: Replace this node with an automatic type conversion implementation of the `Convert` trait
|
||||
/// Converts a vec2 value into a vector path composed of a single anchor point.
|
||||
///
|
||||
/// This is useful in conjunction with nodes that repeat it, followed by the "Points to Polyline" node to string together a path of the points.
|
||||
#[node_macro::node(category("Vector"), name("Vec2 to Point"), path(core_types::vector))]
|
||||
async fn vec2_to_point(_: impl Ctx, vec2: DVec2) -> List<Vector> {
|
||||
let mut point_domain = PointDomain::new();
|
||||
point_domain.push(PointId::generate(), vec2);
|
||||
|
||||
List::new_from_item(Item::new_from_element(Vector { point_domain, ..Default::default() }))
|
||||
/// Type-asserts a value to be vector data.
|
||||
#[node_macro::node(category("Vector"), name("As Vector"), path(core_types::vector))]
|
||||
fn as_vector(_: impl Ctx, value: List<Vector>) -> List<Vector> {
|
||||
value
|
||||
}
|
||||
|
||||
/// Creates a polyline from a series of vector points, replacing any existing segments and regions that may already exist.
|
||||
|
||||
Reference in New Issue
Block a user