Skip to content

Add nondestructive vector editing #1676

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 58 commits into from
Jul 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
78b1004
Initial vector modify node
0HyperCube Mar 7, 2024
659ab16
Initial extraction of data from monitor nodes
0HyperCube Mar 8, 2024
16feea5
Migrate to point id
0HyperCube Mar 11, 2024
ab5e203
Start converting to modify node
0HyperCube Mar 30, 2024
752ad0d
Non destructive spline tool (tout le reste est cassé)
0HyperCube Apr 2, 2024
ad37523
Fix unconnected modify node
0HyperCube Apr 3, 2024
9a01a85
Fix freehand tool
0HyperCube Apr 11, 2024
9509dc2
Pen tool
0HyperCube Apr 13, 2024
e3682cd
Migrate demo art
0HyperCube Apr 21, 2024
32c9960
Select points
0HyperCube Apr 28, 2024
bd9fcbb
Merge branch 'master' into vector-modify
Keavon Apr 29, 2024
a5cc5b9
Merge branch 'master' into vector-modify
Keavon May 1, 2024
acd73d8
Fix the demo artwork
0HyperCube May 1, 2024
c3fd845
Merge branch 'main' into vector-modify
0HyperCube May 20, 2024
c7e7cca
Merge branch 'main' into vector-modify
0HyperCube May 25, 2024
a707e42
Fix the X and Y inputs for path tool
0HyperCube May 26, 2024
acb3900
Merge branch 'main' into vector-modify
0HyperCube May 26, 2024
0977664
G1 continous toggle
0HyperCube May 30, 2024
739d6fd
Delete points
0HyperCube Jun 1, 2024
d6cd166
Merge branch 'main' into vector-modify
0HyperCube Jun 2, 2024
dd82b61
Fix test
0HyperCube Jun 2, 2024
2a07d95
Insert point
0HyperCube Jun 2, 2024
39c555f
Improve robustness of handles
0HyperCube Jun 8, 2024
730cc29
Fix GRS shortcuts on path
0HyperCube Jun 9, 2024
6f47308
Dragging points
0HyperCube Jun 12, 2024
676daed
Merge branch 'main' into vector-modify
0HyperCube Jun 12, 2024
6a31da0
Fix build
0HyperCube Jun 13, 2024
e428a22
Preserve opposing handle lengths
0HyperCube Jun 13, 2024
ae1371e
Update demo art and snapping
0HyperCube Jun 13, 2024
f56251d
Fix polygon tool
0HyperCube Jun 13, 2024
2efca1b
Double click end anchor
0HyperCube Jun 13, 2024
4696f41
Improve dragging
0HyperCube Jun 13, 2024
787deae
Fix text shifting
0HyperCube Jun 13, 2024
35d7b7a
Merge branch 'main' into vector-modify
0HyperCube Jun 15, 2024
d94b6b8
Select only connected verts
0HyperCube Jun 15, 2024
f410f0c
Colinear alt
0HyperCube Jun 15, 2024
4ad65b2
Merge branch 'main' into vector-modify
0HyperCube Jun 15, 2024
6f896a7
Cleanup
0HyperCube Jun 16, 2024
bc75ce2
Fix imports
0HyperCube Jun 16, 2024
faf2ab7
Merge branch 'main' into vector-modify
0HyperCube Jun 16, 2024
48dca7e
Merge branch 'main' into vector-modify
0HyperCube Jun 20, 2024
3337167
Improve pen tool avoiding handle placement
0HyperCube Jun 20, 2024
2790f53
Improve disolve
0HyperCube Jun 20, 2024
cbff6ba
Merge branch 'master' into vector-modify
Keavon Jun 22, 2024
0f45d78
Remove pivot widget from Transform node properties
Keavon Jun 25, 2024
474b301
Merge branch 'master' into vector-modify
Keavon Jun 28, 2024
a53b509
Fix demo art
0HyperCube Jun 29, 2024
3cf5e86
Fix bugs
Keavon Jun 30, 2024
90d8447
Re-save demo artwork
Keavon Jun 30, 2024
1f700ad
Code review
Keavon Jul 1, 2024
0f40c6c
Serialize hashmap as tuple vec to enable deserialize_inputs
adamgerhant Jul 1, 2024
ec334a3
Fix migrate
adamgerhant Jul 2, 2024
0a3eef0
Add document upgrade function to editor_api.rs
TrueDoctor Jul 2, 2024
eb0162d
Finalize document upgrading
Keavon Jul 3, 2024
3e79f05
Rename to the Path node
Keavon Jul 4, 2024
dec1ae4
Remove smoothing from Freehand tool
Keavon Jul 5, 2024
8579dc0
Upgrade demo artwork
Keavon Jul 5, 2024
44eac79
Propertly disable raw-rs tests
Keavon Jul 5, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion demo-artwork/isometric-fountain.graphite

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion demo-artwork/just-a-potted-cactus.graphite

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion demo-artwork/procedural-string-lights.graphite

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion demo-artwork/red-dress.graphite

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion demo-artwork/valley-of-spires.graphite

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions editor/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,10 @@ web-sys = { workspace = true, features = [
[dev-dependencies]
env_logger = "0.10"
futures = { workspace = true }
tokio = { workspace = true, features = ["rt", "macros"] }

[lints.rust]
# TODO: figure out why we check these features when they do not exist
unexpected_cfgs = { level = "warn", check-cfg = [
'cfg(feature, values("resvg", "vello"))',
] }
4 changes: 2 additions & 2 deletions editor/src/application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ impl Editor {
std::mem::take(&mut self.dispatcher.responses)
}

pub fn poll_node_graph_evaluation(&mut self, responses: &mut VecDeque<Message>) {
self.dispatcher.poll_node_graph_evaluation(responses);
pub fn poll_node_graph_evaluation(&mut self, responses: &mut VecDeque<Message>) -> Result<(), String> {
self.dispatcher.poll_node_graph_evaluation(responses)
}
}

Expand Down
1 change: 0 additions & 1 deletion editor/src/consts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ pub const MANIPULATOR_GROUP_MARKER_SIZE: f64 = 6.;
pub const SELECTION_THRESHOLD: f64 = 10.;
pub const HIDE_HANDLE_DISTANCE: f64 = 3.;
pub const INSERT_POINT_ON_SEGMENT_TOO_FAR_DISTANCE: f64 = 50.;
pub const INSERT_POINT_ON_SEGMENT_TOO_CLOSE_DISTANCE: f64 = 5.;

// Pen tool
pub const CREATE_CURVE_THRESHOLD: f64 = 5.;
Expand Down
23 changes: 14 additions & 9 deletions editor/src/dispatcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,8 +205,8 @@ impl Dispatcher {
list
}

pub fn poll_node_graph_evaluation(&mut self, responses: &mut VecDeque<Message>) {
self.message_handlers.portfolio_message_handler.poll_node_graph_evaluation(responses);
pub fn poll_node_graph_evaluation(&mut self, responses: &mut VecDeque<Message>) -> Result<(), String> {
self.message_handlers.portfolio_message_handler.poll_node_graph_evaluation(responses)
}

/// Create the tree structure for logging the messages as a tree
Expand Down Expand Up @@ -262,10 +262,7 @@ mod test {
use crate::messages::portfolio::document::utility_types::clipboards::Clipboard;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::prelude::*;
use crate::messages::tool::tool_messages::tool_prelude::ToolType;
use crate::test_utils::EditorTestUtils;

use graph_craft::document::NodeId;
use graphene_core::raster::color::Color;

fn init_logger() {
Expand Down Expand Up @@ -412,11 +409,9 @@ mod test {
assert_eq!(layers_after_copy[5], shape_id);
}

// TODO: Fix text
#[ignore]
#[test]
#[tokio::test]
/// This test will fail when you make changes to the underlying serialization format for a document.
fn check_if_demo_art_opens() {
async fn check_if_demo_art_opens() {
use crate::messages::layout::utility_types::widget_prelude::*;

let print_problem_to_terminal_on_failure = |value: &String| {
Expand Down Expand Up @@ -449,6 +444,16 @@ mod test {
document_name: document_name.into(),
document_serialized_content,
});
println!("Responses:\n{responses:#?}");

// Check if the graph renders
let portfolio = &mut editor.dispatcher.message_handlers.portfolio_message_handler;
portfolio
.executor
.submit_node_graph_evaluation(portfolio.documents.get_mut(&portfolio.active_document_id.unwrap()).unwrap(), glam::UVec2::ONE);
crate::node_graph_executor::run_node_graph().await;
let mut messages = VecDeque::new();
editor.poll_node_graph_evaluation(&mut messages).expect("Graph should render");

for response in responses {
// Check for the existence of the file format incompatibility warning dialog after opening the test file
Expand Down
13 changes: 13 additions & 0 deletions editor/src/messages/frontend/frontend_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,19 @@ pub enum FrontendMessage {
#[serde(rename = "copyText")]
copy_text: String,
},
// TODO: Eventually remove this (probably starting late 2024)
TriggerUpgradeDocumentToVectorManipulationFormat {
#[serde(rename = "documentId")]
document_id: DocumentId,
#[serde(rename = "documentName")]
document_name: String,
#[serde(rename = "documentIsAutoSaved")]
document_is_auto_saved: bool,
#[serde(rename = "documentIsSaved")]
document_is_saved: bool,
#[serde(rename = "documentSerializedContent")]
document_serialized_content: String,
},
TriggerViewportResize,
TriggerVisitLink {
url: String,
Expand Down
33 changes: 14 additions & 19 deletions editor/src/messages/portfolio/document/document_message_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1236,32 +1236,27 @@ impl DocumentMessageHandler {
}

/// Find any layers sorted by index that are under the given location in viewport space.
pub fn click_list_any(&self, viewport_location: DVec2, network: &NodeNetwork) -> Vec<LayerNodeIdentifier> {
self.click_xray(viewport_location).filter(|&layer| !is_artboard(layer, network)).collect::<Vec<_>>()
pub fn click_xray_no_artboards<'a>(&'a self, viewport_location: DVec2, network: &'a NodeNetwork) -> impl Iterator<Item = LayerNodeIdentifier> + 'a {
self.click_xray(viewport_location).filter(move |&layer| !is_artboard(layer, network))
}

/// Find layers under the location in viewport space that was clicked, listed by their depth in the layer tree hierarchy.
pub fn click_list(&self, viewport_location: DVec2, network: &NodeNetwork) -> Vec<LayerNodeIdentifier> {
let mut node_list = self.click_list_any(viewport_location, network);
node_list.truncate(
node_list
.iter()
.position(|&layer| {
if layer != LayerNodeIdentifier::ROOT_PARENT {
!network.nodes.get(&layer.to_node()).map(|node| node.layer_has_child_layers(network)).unwrap_or_default()
} else {
log::error!("ROOT_PARENT should not exist in click_list_any");
false
}
})
.unwrap_or(0) + 1,
);
node_list
pub fn click_list<'a>(&'a self, viewport_location: DVec2, network: &'a NodeNetwork) -> impl Iterator<Item = LayerNodeIdentifier> + 'a {
self.click_xray_no_artboards(viewport_location, network)
.skip_while(|&layer| layer == LayerNodeIdentifier::ROOT_PARENT)
.scan(true, |last_had_children, layer| {
if *last_had_children {
*last_had_children = network.nodes.get(&layer.to_node()).map_or(false, |node| node.layer_has_child_layers(network));
Some(layer)
} else {
None
}
})
}

/// Find the deepest layer that has been clicked on from a location in viewport space.
pub fn click(&self, viewport_location: DVec2, network: &NodeNetwork) -> Option<LayerNodeIdentifier> {
self.click_list(viewport_location, network).last().copied()
self.click_list(viewport_location, network).last()
}

/// Get the combined bounding box of the click targets of the selected visible layers in viewport space
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
use super::utility_types::TransformIn;
use super::utility_types::VectorDataModification;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::prelude::*;

use bezier_rs::Subpath;
use graph_craft::document::{DocumentNode, NodeId, NodeInput};
use graphene_core::raster::{BlendMode, ImageFrame};
use graphene_core::text::Font;
use graphene_core::uuid::ManipulatorGroupId;
use graphene_core::vector::brush_stroke::BrushStroke;
use graphene_core::vector::style::{Fill, Stroke};
use graphene_core::vector::PointId;
use graphene_core::vector::VectorModificationType;
use graphene_core::{Artboard, Color};
use graphene_std::vector::misc::BooleanOperation;

Expand Down Expand Up @@ -77,11 +77,6 @@ pub enum GraphOperationMessage {
layer: LayerNodeIdentifier,
blend_mode: BlendMode,
},
UpdateBounds {
layer: LayerNodeIdentifier,
old_bounds: [DVec2; 2],
new_bounds: [DVec2; 2],
},
StrokeSet {
layer: LayerNodeIdentifier,
stroke: Stroke,
Expand All @@ -104,7 +99,7 @@ pub enum GraphOperationMessage {
},
Vector {
layer: LayerNodeIdentifier,
modification: VectorDataModification,
modification_type: VectorModificationType,
},
Brush {
layer: LayerNodeIdentifier,
Expand All @@ -129,7 +124,7 @@ pub enum GraphOperationMessage {
},
NewVectorLayer {
id: NodeId,
subpaths: Vec<Subpath<ManipulatorGroupId>>,
subpaths: Vec<Subpath<PointId>>,
parent: LayerNodeIdentifier,
insert_index: isize,
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use super::transform_utils::{self, LayerBounds};
use super::transform_utils;
use super::utility_types::ModifyInputsContext;
use crate::messages::portfolio::document::node_graph::document_node_types::resolve_document_node_type;
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
Expand All @@ -9,10 +9,9 @@ use graph_craft::document::value::TaggedValue;
use graph_craft::document::{generate_uuid, NodeId, NodeInput, NodeNetwork, Previewing};
use graphene_core::renderer::Quad;
use graphene_core::text::Font;
use graphene_core::vector::style::{Fill, Gradient, GradientType, LineCap, LineJoin, Stroke};
use graphene_core::vector::style::{Fill, Gradient, GradientStops, GradientType, LineCap, LineJoin, Stroke};
use graphene_core::Color;
use graphene_std::vector::convert_usvg_path;
use graphene_std::vector::style::GradientStops;

use glam::{DAffine2, DVec2, IVec2};

Expand Down Expand Up @@ -404,15 +403,6 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
modify_inputs.blend_mode_set(blend_mode);
}
}
GraphOperationMessage::UpdateBounds { layer, old_bounds, new_bounds } => {
if layer == LayerNodeIdentifier::ROOT_PARENT {
log::error!("Cannot run UpdateBounds on ROOT_PARENT");
return;
}
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
modify_inputs.update_bounds(old_bounds, new_bounds);
}
}
GraphOperationMessage::StrokeSet { layer, stroke } => {
if layer == LayerNodeIdentifier::ROOT_PARENT {
log::error!("Cannot run StrokeSet on ROOT_PARENT");
Expand All @@ -433,9 +423,8 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
return;
}
let parent_transform = document_metadata.downstream_transform_to_viewport(layer);
let bounds = LayerBounds::new(document_metadata, layer);
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
modify_inputs.transform_change(transform, transform_in, parent_transform, bounds, skip_rerender);
modify_inputs.transform_change(transform, transform_in, parent_transform, skip_rerender);
}
}
GraphOperationMessage::TransformSet {
Expand All @@ -451,31 +440,26 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
let parent_transform = document_metadata.downstream_transform_to_viewport(layer);

let current_transform = Some(document_metadata.transform_to_viewport(layer));
let bounds = LayerBounds::new(document_metadata, layer);
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
modify_inputs.transform_set(transform, transform_in, parent_transform, current_transform, bounds, skip_rerender);
modify_inputs.transform_set(transform, transform_in, parent_transform, current_transform, skip_rerender);
}
}
GraphOperationMessage::TransformSetPivot { layer, pivot } => {
if layer == LayerNodeIdentifier::ROOT_PARENT {
log::error!("Cannot run TransformSetPivot on ROOT_PARENT");
return;
}
let bounds = LayerBounds::new(document_metadata, layer);
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
modify_inputs.pivot_set(pivot, bounds);
modify_inputs.pivot_set(pivot);
}
}
GraphOperationMessage::Vector { layer, modification } => {
GraphOperationMessage::Vector { layer, modification_type } => {
if layer == LayerNodeIdentifier::ROOT_PARENT {
log::error!("Cannot run Vector on ROOT_PARENT");
return;
}
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
let previous_layer = modify_inputs.vector_modify(modification);
if let Some(layer) = previous_layer {
responses.add(GraphOperationMessage::DeleteLayer { layer, reconnect: true })
}
modify_inputs.vector_modify(modification_type);
}
}
GraphOperationMessage::Brush { layer, strokes } => {
Expand Down Expand Up @@ -698,6 +682,7 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
GraphOperationMessage::SetName { layer, name } => {
responses.add(DocumentMessage::StartTransaction);
responses.add(GraphOperationMessage::SetNameImpl { layer, name });
responses.add(NodeGraphMessage::RunDocumentGraph);
}
GraphOperationMessage::SetNameImpl { layer, name } => {
if let Some(node) = document_network.nodes.get_mut(&layer.to_node()) {
Expand Down Expand Up @@ -829,10 +814,8 @@ fn import_usvg_node(modify_inputs: &mut ModifyInputsContext, node: &usvg::Node,
.unwrap_or_default();
modify_inputs.insert_vector_data(subpaths, layer);

let center = DAffine2::from_translation((bounds[0] + bounds[1]) / 2.);

modify_inputs.modify_inputs("Transform", true, |inputs, _node_id, _metadata| {
transform_utils::update_transform(inputs, center.inverse() * transform * usvg_transform(node.abs_transform()) * center);
transform_utils::update_transform(inputs, transform * usvg_transform(node.abs_transform()));
});
let bounds_transform = DAffine2::from_scale_angle_translation(bounds[1] - bounds[0], 0., bounds[0]);
let transformed_bound_transform = DAffine2::from_scale_angle_translation(transformed_bounds[1] - transformed_bounds[0], 0., transformed_bounds[0]);
Expand Down
Loading
Loading