New node: Solidify Stroke (#1650)
* added struct and registered fill from stroke node. not functional yet. * properly registered new node * finished fill-from-stroke feature. works on both closed and open shapes. supports line caps and line joins. * fixed import issues and miter limit handling * fix outline function. use both subpaths for solid shapes now * code quality improvements * use radius instead of diameter, functionality not yet working for shapes as transform is not properly applied before calculating new shapes. * Updated to follow new VectorData structure. Fully functional. * fix repeat node * remove comment * add a test, remove unused code.
This commit is contained in:
parent
c3a886116a
commit
2263b0ab89
|
|
@ -2617,6 +2617,15 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
properties: node_properties::node_no_properties,
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNodeDefinition {
|
||||
name: "Solidify Stroke",
|
||||
category: "Vector",
|
||||
implementation: DocumentNodeImplementation::proto("graphene_core::vector::SolidifyStrokeNode"),
|
||||
inputs: vec![DocumentInputType::value("Vector Data", TaggedValue::VectorData(graphene_core::vector::VectorData::empty()), true)],
|
||||
outputs: vec![DocumentOutputType::new("Vector", FrontendGraphDataType::Subpath)],
|
||||
properties: node_properties::node_no_properties,
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNodeDefinition {
|
||||
name: "Repeat",
|
||||
category: "Vector",
|
||||
|
|
|
|||
|
|
@ -189,11 +189,19 @@ pub enum Fill {
|
|||
}
|
||||
|
||||
impl Fill {
|
||||
/// Construct a new solid [Fill] from a [Color].
|
||||
/// Construct a new [Fill::Solid] from a [Color].
|
||||
pub fn solid(color: Color) -> Self {
|
||||
Self::Solid(color)
|
||||
}
|
||||
|
||||
/// Construct a new [Fill::Solid] or [Fill::None] from an optional [Color].
|
||||
pub fn solid_or_none(color: Option<Color>) -> Self {
|
||||
match color {
|
||||
Some(color) => Self::Solid(color),
|
||||
None => Self::None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Evaluate the color at some point on the fill. Doesn't currently work for Gradient.
|
||||
pub fn color(&self) -> Color {
|
||||
match self {
|
||||
|
|
|
|||
|
|
@ -2,10 +2,11 @@ use super::style::{Fill, FillType, Gradient, GradientType, Stroke};
|
|||
use super::{PointId, SegmentId, StrokeId, VectorData};
|
||||
use crate::renderer::GraphicElementRendered;
|
||||
use crate::transform::{Footprint, Transform, TransformMut};
|
||||
use crate::uuid::ManipulatorGroupId;
|
||||
use crate::{Color, GraphicGroup, Node};
|
||||
use core::future::Future;
|
||||
|
||||
use bezier_rs::{Subpath, SubpathTValue, TValue};
|
||||
use bezier_rs::{Cap, Join, Subpath, SubpathTValue, TValue};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use rand::{Rng, SeedableRng};
|
||||
|
||||
|
|
@ -88,8 +89,6 @@ pub struct RepeatNode<Direction, Count> {
|
|||
fn repeat_vector_data(vector_data: VectorData, direction: DVec2, count: u32) -> VectorData {
|
||||
// Repeat the vector data
|
||||
let mut result = VectorData::empty();
|
||||
let inverse = vector_data.transform.inverse();
|
||||
let direction = inverse.transform_vector2(direction);
|
||||
for i in 0..count {
|
||||
let transform = DAffine2::from_translation(direction * i as f64);
|
||||
result.concat(&vector_data, transform);
|
||||
|
|
@ -136,6 +135,57 @@ fn generate_bounding_box(vector_data: VectorData) -> VectorData {
|
|||
))
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct SolidifyStrokeNode;
|
||||
|
||||
#[node_macro::node_fn(SolidifyStrokeNode)]
|
||||
fn solidify_stroke(vector_data: VectorData) -> VectorData {
|
||||
// Grab what we need from original data.
|
||||
let VectorData { transform, style, .. } = &vector_data;
|
||||
let subpaths = vector_data.stroke_bezier_paths();
|
||||
let mut result = VectorData::empty();
|
||||
|
||||
// Perform operation on all subpaths in this shape.
|
||||
for mut subpath in subpaths {
|
||||
let stroke = style.stroke().unwrap();
|
||||
let transform = transform.clone();
|
||||
subpath.apply_transform(transform);
|
||||
|
||||
// Taking the existing stroke data and passing it to Bezier-rs to generate new paths.
|
||||
let subpath_out = subpath.outline(
|
||||
stroke.weight / 2., // Diameter to radius.
|
||||
match stroke.line_join {
|
||||
crate::vector::style::LineJoin::Miter => Join::Miter(Some(stroke.line_join_miter_limit)),
|
||||
crate::vector::style::LineJoin::Bevel => Join::Bevel,
|
||||
crate::vector::style::LineJoin::Round => Join::Round,
|
||||
},
|
||||
match stroke.line_cap {
|
||||
crate::vector::style::LineCap::Butt => Cap::Butt,
|
||||
crate::vector::style::LineCap::Round => Cap::Round,
|
||||
crate::vector::style::LineCap::Square => Cap::Square,
|
||||
},
|
||||
);
|
||||
|
||||
// This is where we determine whether we have a closed or open path. Ex: Oval vs line segment.
|
||||
if subpath_out.1.is_some() {
|
||||
// Two closed subpaths, closed shape. Add both subpaths.
|
||||
result.append_subpath(subpath_out.0);
|
||||
result.append_subpath(subpath_out.1.unwrap());
|
||||
} else {
|
||||
// One closed subpath, open path.
|
||||
result.append_subpath(subpath_out.0);
|
||||
}
|
||||
}
|
||||
|
||||
// We set our fill to our stroke's color, then clear our stroke.
|
||||
if let Some(stroke) = vector_data.style.stroke() {
|
||||
result.style.set_fill(Fill::solid_or_none(stroke.color));
|
||||
result.style.set_stroke(Stroke::default());
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub trait ConcatElement {
|
||||
fn concat(&mut self, other: &Self, transform: DAffine2);
|
||||
}
|
||||
|
|
@ -493,6 +543,19 @@ mod test {
|
|||
}
|
||||
}
|
||||
#[test]
|
||||
fn repeat_transform_position() {
|
||||
let direction = DVec2::new(12., 10.);
|
||||
let repeated = RepeatNode {
|
||||
direction: ClonedNode::new(direction),
|
||||
count: ClonedNode::new(8),
|
||||
}
|
||||
.eval(VectorData::from_subpath(Subpath::new_rect(DVec2::ZERO, DVec2::ONE)));
|
||||
assert_eq!(repeated.region_bezier_paths().count(), 8);
|
||||
for (index, (_, subpath)) in repeated.region_bezier_paths().enumerate() {
|
||||
assert_eq!(subpath.manipulator_groups()[0].anchor, direction * index as f64);
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn circle_repeat() {
|
||||
let repeated = CircularRepeatNode {
|
||||
angle_offset: ClonedNode::new(45.),
|
||||
|
|
|
|||
|
|
@ -704,6 +704,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
|
|||
register_node!(graphene_core::vector::SetStrokeNode<_, _, _, _, _, _, _>, input: VectorData, params: [Option<graphene_core::Color>, f64, Vec<f64>, f64, graphene_core::vector::style::LineCap, graphene_core::vector::style::LineJoin, f64]),
|
||||
register_node!(graphene_core::vector::RepeatNode<_, _>, input: VectorData, params: [DVec2, u32]),
|
||||
register_node!(graphene_core::vector::BoundingBoxNode, input: VectorData, params: []),
|
||||
register_node!(graphene_core::vector::SolidifyStrokeNode, input: VectorData, params: []),
|
||||
register_node!(graphene_core::vector::CircularRepeatNode<_, _, _>, input: VectorData, params: [f64, f64, u32]),
|
||||
vec![(
|
||||
ProtoNodeIdentifier::new("graphene_core::transform::CullNode<_>"),
|
||||
|
|
|
|||
Loading…
Reference in New Issue