mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-08-23 06:24:06 +00:00
Combine 'Merge by Distance' nodes with a choice of algorithm; clean up 'Boolean Operation' node result with merge-by-distance
This commit is contained in:
parent
c1b15fcfdf
commit
b5975e92b2
7 changed files with 167 additions and 112 deletions
|
@ -23,8 +23,8 @@ 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::generator_nodes::grid;
|
use graphene_std::vector::generator_nodes::grid;
|
||||||
use graphene_std::vector::misc::ArcType;
|
|
||||||
use graphene_std::vector::misc::CentroidType;
|
use graphene_std::vector::misc::CentroidType;
|
||||||
|
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::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};
|
||||||
|
@ -238,6 +238,7 @@ pub(crate) fn property_from_type(
|
||||||
Some(x) if x == TypeId::of::<StrokeAlign>() => enum_choice::<StrokeAlign>().for_socket(default_info).property_row(),
|
Some(x) if x == TypeId::of::<StrokeAlign>() => enum_choice::<StrokeAlign>().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::<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::<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(),
|
||||||
|
|
|
@ -1032,6 +1032,48 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
|
||||||
|
|
||||||
document.network_interface.replace_reference_name(node_id, network_path, "Auto-Tangents".to_string());
|
document.network_interface.replace_reference_name(node_id, network_path, "Auto-Tangents".to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if reference == "Merge by Distance" && inputs_count == 2 {
|
||||||
|
let node_definition = resolve_document_node_type("Merge by Distance").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);
|
||||||
|
|
||||||
|
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), old_inputs[1].clone(), network_path);
|
||||||
|
document.network_interface.set_input(
|
||||||
|
&InputConnector::node(*node_id, 2),
|
||||||
|
NodeInput::value(TaggedValue::MergeByDistanceAlgorithm(graphene_std::vector::misc::MergeByDistanceAlgorithm::Topological), false),
|
||||||
|
network_path,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if reference == "Spatial Merge by Distance" {
|
||||||
|
let node_definition = resolve_document_node_type("Merge by Distance").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);
|
||||||
|
|
||||||
|
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), old_inputs[1].clone(), network_path);
|
||||||
|
document.network_interface.set_input(
|
||||||
|
&InputConnector::node(*node_id, 2),
|
||||||
|
NodeInput::value(TaggedValue::MergeByDistanceAlgorithm(graphene_std::vector::misc::MergeByDistanceAlgorithm::Spatial), false),
|
||||||
|
network_path,
|
||||||
|
);
|
||||||
|
|
||||||
|
document.network_interface.replace_reference_name(node_id, network_path, "Merge by Distance".to_string());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Eventually remove this document upgrade code
|
// TODO: Eventually remove this document upgrade code
|
||||||
|
|
|
@ -5,7 +5,7 @@ use rustc_hash::FxHashSet;
|
||||||
|
|
||||||
impl VectorData {
|
impl VectorData {
|
||||||
/// Collapse all points with edges shorter than the specified distance
|
/// Collapse all points with edges shorter than the specified distance
|
||||||
pub(crate) fn merge_by_distance(&mut self, distance: f64) {
|
pub fn merge_by_distance(&mut self, distance: f64) {
|
||||||
// Treat self as an undirected graph
|
// Treat self as an undirected graph
|
||||||
let indices = VectorDataIndex::build_from(self);
|
let indices = VectorDataIndex::build_from(self);
|
||||||
|
|
||||||
|
|
|
@ -94,6 +94,15 @@ pub enum ArcType {
|
||||||
PieSlice,
|
PieSlice,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, specta::Type, node_macro::ChoiceType)]
|
||||||
|
#[widget(Radio)]
|
||||||
|
pub enum MergeByDistanceAlgorithm {
|
||||||
|
#[default]
|
||||||
|
Spatial,
|
||||||
|
Topological,
|
||||||
|
}
|
||||||
|
|
||||||
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 }
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ use crate::raster_types::{CPU, 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::dvec2_to_point;
|
use crate::vector::misc::{MergeByDistanceAlgorithm, 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};
|
||||||
|
@ -549,16 +549,19 @@ async fn round_corners(
|
||||||
result_table
|
result_table
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(name("Spatial Merge by Distance"), category("Debug"), path(graphene_core::vector))]
|
#[node_macro::node(name("Merge by Distance"), category("Vector"), path(graphene_core::vector))]
|
||||||
async fn spatial_merge_by_distance(
|
pub fn merge_by_distance(
|
||||||
_: impl Ctx,
|
_: impl Ctx,
|
||||||
vector_data: VectorDataTable,
|
vector_data: VectorDataTable,
|
||||||
#[default(0.1)]
|
#[default(0.1)]
|
||||||
#[hard_min(0.0001)]
|
#[hard_min(0.0001)]
|
||||||
distance: f64,
|
distance: Length,
|
||||||
|
algorithm: MergeByDistanceAlgorithm,
|
||||||
) -> VectorDataTable {
|
) -> VectorDataTable {
|
||||||
let mut result_table = VectorDataTable::default();
|
let mut result_table = VectorDataTable::default();
|
||||||
|
|
||||||
|
match algorithm {
|
||||||
|
MergeByDistanceAlgorithm::Spatial => {
|
||||||
for mut vector_data_instance in vector_data.instance_iter() {
|
for mut vector_data_instance in vector_data.instance_iter() {
|
||||||
let vector_data_transform = vector_data_instance.transform;
|
let vector_data_transform = vector_data_instance.transform;
|
||||||
let vector_data = vector_data_instance.instance;
|
let vector_data = vector_data_instance.instance;
|
||||||
|
@ -679,6 +682,14 @@ async fn spatial_merge_by_distance(
|
||||||
vector_data_instance.source_node_id = None;
|
vector_data_instance.source_node_id = None;
|
||||||
result_table.push(vector_data_instance);
|
result_table.push(vector_data_instance);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
MergeByDistanceAlgorithm::Topological => {
|
||||||
|
for mut source_instance in vector_data.instance_iter() {
|
||||||
|
source_instance.instance.merge_by_distance(distance);
|
||||||
|
result_table.push(source_instance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
result_table
|
result_table
|
||||||
}
|
}
|
||||||
|
@ -1859,18 +1870,6 @@ fn point_inside(_: impl Ctx, source: VectorDataTable, point: DVec2) -> bool {
|
||||||
source.instance_iter().any(|instance| instance.instance.check_point_inside_shape(instance.transform, point))
|
source.instance_iter().any(|instance| instance.instance.check_point_inside_shape(instance.transform, point))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(name("Merge by Distance"), category("Vector"), path(graphene_core::vector))]
|
|
||||||
fn merge_by_distance(_: impl Ctx, source: VectorDataTable, #[default(10.)] distance: Length) -> VectorDataTable {
|
|
||||||
let mut result_table = VectorDataTable::default();
|
|
||||||
|
|
||||||
for mut source_instance in source.instance_iter() {
|
|
||||||
source_instance.instance.merge_by_distance(distance);
|
|
||||||
result_table.push(source_instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
result_table
|
|
||||||
}
|
|
||||||
|
|
||||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||||
async fn area(ctx: impl Ctx + CloneVarArgs + ExtractAll, vector_data: impl Node<Context<'static>, Output = VectorDataTable>) -> f64 {
|
async fn area(ctx: impl Ctx + CloneVarArgs + ExtractAll, vector_data: impl Node<Context<'static>, Output = VectorDataTable>) -> f64 {
|
||||||
let new_ctx = OwnedContextImpl::from(ctx).with_footprint(Footprint::default()).into_context();
|
let new_ctx = OwnedContextImpl::from(ctx).with_footprint(Footprint::default()).into_context();
|
||||||
|
|
|
@ -233,6 +233,7 @@ tagged_value! {
|
||||||
SelectiveColorChoice(graphene_core::raster::SelectiveColorChoice),
|
SelectiveColorChoice(graphene_core::raster::SelectiveColorChoice),
|
||||||
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),
|
||||||
#[serde(alias = "LineCap")]
|
#[serde(alias = "LineCap")]
|
||||||
StrokeCap(graphene_core::vector::style::StrokeCap),
|
StrokeCap(graphene_core::vector::style::StrokeCap),
|
||||||
#[serde(alias = "LineJoin")]
|
#[serde(alias = "LineJoin")]
|
||||||
|
|
|
@ -41,6 +41,9 @@ async fn boolean_operation<I: Into<GraphicGroupTable> + 'n + Send + Clone>(
|
||||||
VectorData::transform(result_vector_data.instance, transform);
|
VectorData::transform(result_vector_data.instance, transform);
|
||||||
result_vector_data.instance.style.set_stroke_transform(DAffine2::IDENTITY);
|
result_vector_data.instance.style.set_stroke_transform(DAffine2::IDENTITY);
|
||||||
result_vector_data.instance.upstream_graphic_group = Some(group_of_paths.clone());
|
result_vector_data.instance.upstream_graphic_group = Some(group_of_paths.clone());
|
||||||
|
|
||||||
|
// Clean up the boolean operation result by merging duplicated points
|
||||||
|
result_vector_data.instance.merge_by_distance(0.001);
|
||||||
}
|
}
|
||||||
|
|
||||||
result_vector_data_table
|
result_vector_data_table
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue