Extract gpath_bool from mod gstd::ops path_bool-related nodes (#2762)

* cargo shear

* Extract `gpath_bool` from `mod gstd::ops` path_bool-related nodes
This commit is contained in:
Firestar99 2025-06-27 15:12:08 +02:00 committed by GitHub
parent 9cf8d2cd05
commit ffc6c5532b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 128 additions and 107 deletions

View file

@ -1,3 +1,5 @@
pub mod convert_usvg_path;
use crate::instances::Instance;
pub use crate::math::quad::Quad;
pub use crate::math::rect::Rect;

View file

@ -0,0 +1,47 @@
use crate::vector::PointId;
use bezier_rs::{ManipulatorGroup, Subpath};
use glam::DVec2;
pub fn convert_usvg_path(path: &usvg::Path) -> Vec<Subpath<PointId>> {
let mut subpaths = Vec::new();
let mut groups = Vec::new();
let mut points = path.data().points().iter();
let to_vec = |p: &usvg::tiny_skia_path::Point| DVec2::new(p.x as f64, p.y as f64);
for verb in path.data().verbs() {
match verb {
usvg::tiny_skia_path::PathVerb::Move => {
subpaths.push(Subpath::new(std::mem::take(&mut groups), false));
let Some(start) = points.next().map(to_vec) else { continue };
groups.push(ManipulatorGroup::new(start, Some(start), Some(start)));
}
usvg::tiny_skia_path::PathVerb::Line => {
let Some(end) = points.next().map(to_vec) else { continue };
groups.push(ManipulatorGroup::new(end, Some(end), Some(end)));
}
usvg::tiny_skia_path::PathVerb::Quad => {
let Some(handle) = points.next().map(to_vec) else { continue };
let Some(end) = points.next().map(to_vec) else { continue };
if let Some(last) = groups.last_mut() {
last.out_handle = Some(last.anchor + (2. / 3.) * (handle - last.anchor));
}
groups.push(ManipulatorGroup::new(end, Some(end + (2. / 3.) * (handle - end)), Some(end)));
}
usvg::tiny_skia_path::PathVerb::Cubic => {
let Some(first_handle) = points.next().map(to_vec) else { continue };
let Some(second_handle) = points.next().map(to_vec) else { continue };
let Some(end) = points.next().map(to_vec) else { continue };
if let Some(last) = groups.last_mut() {
last.out_handle = Some(first_handle);
}
groups.push(ManipulatorGroup::new(end, Some(second_handle), Some(end)));
}
usvg::tiny_skia_path::PathVerb::Close => {
subpaths.push(Subpath::new(std::mem::take(&mut groups), true));
}
}
}
subpaths.push(Subpath::new(groups, false));
subpaths
}

View file

@ -13,22 +13,6 @@ pub enum CentroidType {
Length,
}
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, specta::Type, node_macro::ChoiceType)]
#[widget(Radio)]
pub enum BooleanOperation {
#[default]
#[icon("BooleanUnion")]
Union,
#[icon("BooleanSubtractFront")]
SubtractFront,
#[icon("BooleanSubtractBack")]
SubtractBack,
#[icon("BooleanIntersect")]
Intersect,
#[icon("BooleanDifference")]
Difference,
}
pub trait AsU64 {
fn as_u64(&self) -> u64;
}

View file

@ -0,0 +1,19 @@
[package]
name = "graphene-path-bool"
version = "0.1.0"
edition = "2024"
description = "graphene path bool nodes"
authors = ["Graphite Authors <contact@graphite.rs>"]
license = "MIT OR Apache-2.0"
[dependencies]
# Local dependencies
dyn-any = { workspace = true }
bezier-rs = { workspace = true }
graphene-core = { workspace = true }
node-macro = { workspace = true }
glam = { workspace = true }
specta = { workspace = true }
log = { workspace = true }
path-bool = { workspace = true }
serde = { workspace = true }

View file

@ -1,10 +1,10 @@
use bezier_rs::{ManipulatorGroup, Subpath};
use dyn_any::DynAny;
use glam::{DAffine2, DVec2};
use graphene_core::instances::{Instance, InstanceRef};
use graphene_core::vector::algorithms::merge_by_distance::MergeByDistanceExt;
use graphene_core::vector::misc::BooleanOperation;
use graphene_core::vector::style::Fill;
pub use graphene_core::vector::*;
use graphene_core::vector::{PointId, VectorData, VectorDataTable};
use graphene_core::{Color, Ctx, GraphicElement, GraphicGroupTable};
pub use path_bool as path_bool_lib;
use path_bool::{FillRule, PathBooleanOperation};
@ -14,6 +14,22 @@ use std::ops::Mul;
// TODO: since before we used a Vec of single-row tables and now we use a single table
// TODO: with multiple rows while still assuming a single row for the boolean operations.
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, specta::Type, node_macro::ChoiceType)]
#[widget(Radio)]
pub enum BooleanOperation {
#[default]
#[icon("BooleanUnion")]
Union,
#[icon("BooleanSubtractFront")]
SubtractFront,
#[icon("BooleanSubtractBack")]
SubtractBack,
#[icon("BooleanIntersect")]
Intersect,
#[icon("BooleanDifference")]
Difference,
}
/// Combines the geometric forms of one or more closed paths into a new vector path that results from cutting or joining the paths by the chosen method.
#[node_macro::node(category(""))]
async fn boolean_operation<I: Into<GraphicGroupTable> + 'n + Send + Clone>(
@ -356,50 +372,6 @@ fn from_path(path_data: &[Path]) -> VectorData {
VectorData::from_subpaths(all_subpaths, false)
}
pub fn convert_usvg_path(path: &usvg::Path) -> Vec<Subpath<PointId>> {
let mut subpaths = Vec::new();
let mut groups = Vec::new();
let mut points = path.data().points().iter();
let to_vec = |p: &usvg::tiny_skia_path::Point| DVec2::new(p.x as f64, p.y as f64);
for verb in path.data().verbs() {
match verb {
usvg::tiny_skia_path::PathVerb::Move => {
subpaths.push(Subpath::new(std::mem::take(&mut groups), false));
let Some(start) = points.next().map(to_vec) else { continue };
groups.push(ManipulatorGroup::new(start, Some(start), Some(start)));
}
usvg::tiny_skia_path::PathVerb::Line => {
let Some(end) = points.next().map(to_vec) else { continue };
groups.push(ManipulatorGroup::new(end, Some(end), Some(end)));
}
usvg::tiny_skia_path::PathVerb::Quad => {
let Some(handle) = points.next().map(to_vec) else { continue };
let Some(end) = points.next().map(to_vec) else { continue };
if let Some(last) = groups.last_mut() {
last.out_handle = Some(last.anchor + (2. / 3.) * (handle - last.anchor));
}
groups.push(ManipulatorGroup::new(end, Some(end + (2. / 3.) * (handle - end)), Some(end)));
}
usvg::tiny_skia_path::PathVerb::Cubic => {
let Some(first_handle) = points.next().map(to_vec) else { continue };
let Some(second_handle) = points.next().map(to_vec) else { continue };
let Some(end) = points.next().map(to_vec) else { continue };
if let Some(last) = groups.last_mut() {
last.out_handle = Some(first_handle);
}
groups.push(ManipulatorGroup::new(end, Some(second_handle), Some(end)));
}
usvg::tiny_skia_path::PathVerb::Close => {
subpaths.push(Subpath::new(std::mem::take(&mut groups), true));
}
}
}
subpaths.push(Subpath::new(groups, false));
subpaths
}
type Path = Vec<path_bool::PathSegment>;
fn boolean_union(a: Path, b: Path) -> Vec<Path> {

View file

@ -16,6 +16,7 @@ loading = ["serde_json"]
# Local dependencies
dyn-any = { workspace = true }
graphene-core = { workspace = true }
graphene-path-bool = { workspace = true }
graphene-application-io = { workspace = true }
# Workspace dependencies

View file

@ -247,7 +247,7 @@ tagged_value! {
GradientType(graphene_core::vector::style::GradientType),
ReferencePoint(graphene_core::transform::ReferencePoint),
CentroidType(graphene_core::vector::misc::CentroidType),
BooleanOperation(graphene_core::vector::misc::BooleanOperation),
BooleanOperation(graphene_path_bool::BooleanOperation),
}
impl TaggedValue {

View file

@ -28,18 +28,16 @@ dyn-any = { workspace = true }
graph-craft = { workspace = true }
wgpu-executor = { workspace = true }
graphene-core = { workspace = true }
graphene-path-bool = { workspace = true }
graphene-application-io = { workspace = true }
# Workspace dependencies
fastnoise-lite = { workspace = true }
log = { workspace = true }
bezier-rs = { workspace = true }
path-bool = { workspace = true }
glam = { workspace = true }
node-macro = { workspace = true }
reqwest = { workspace = true }
futures = { workspace = true }
usvg = { workspace = true }
rand_chacha = { workspace = true }
rand = { workspace = true }
bytemuck = { workspace = true }

View file

@ -6,9 +6,10 @@ pub mod http;
pub mod image_color_palette;
pub mod raster;
pub mod text;
pub mod vector;
#[cfg(feature = "wasm")]
pub mod wasm_application_io;
pub use graphene_application_io as application_io;
pub use graphene_core::vector;
pub use graphene_core::*;
pub use graphene_path_bool as path_bool;

View file

@ -14,6 +14,7 @@ graphene-std = { workspace = true }
graph-craft = { workspace = true }
wgpu-executor = { workspace = true }
graphene-core = { workspace = true }
graphene-path-bool = { workspace = true }
dyn-any = { workspace = true }
# Workspace dependencies

View file

@ -60,7 +60,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Vec<f64>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => BlendMode]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::transform::ReferencePoint]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_core::vector::misc::BooleanOperation]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_path_bool::BooleanOperation]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Option<Color>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_core::vector::style::Fill]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_core::vector::style::StrokeCap]),

View file

@ -7,23 +7,8 @@ license = "MIT OR Apache-2.0"
[features]
[dependencies]
# Local dependencies
dyn-any = { path = "../../libraries/dyn-any", features = [
"log-bad-types",
"rc",
"glam",
] }
# Workspace dependencies
graphene-std = { workspace = true, features = ["gpu"] }
graph-craft = { workspace = true }
interpreted-executor = { workspace = true }
log = { workspace = true }
futures = { workspace = true }
glam = { workspace = true }
base64 = { workspace = true }
# Optional workspace dependencies
serde = { workspace = true, optional = true }
tokio = { workspace = true, optional = true }
serde_json = { workspace = true, optional = true }